diff --git a/.gitignore b/.gitignore index 401058f1..ac04b2f6 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ dist-ssr playwright-reports test-results .playwright-mcp +debug.png scripts/outputs/ # Claude Code @@ -43,3 +44,7 @@ public/relay-test.html .worktrees/ docs/demo-videos/.last-run.json docs/demo-videos/e2e-*/ + +# OpenWebUI demo auth state — contains session tokens, never commit +.playwright/owui-auth.json +logs/ diff --git a/.playwright/demo-restart.sh b/.playwright/demo-restart.sh new file mode 100755 index 00000000..791e5793 --- /dev/null +++ b/.playwright/demo-restart.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# demo-restart.sh — Clean restart for OWUI relay demo session. +# +# Run this from the terminal BEFORE asking Claude to run pizza-demo-setup.js. +# Kills stale playwright-mcp + Chrome instances and removes Chrome lock files +# so the MCP browser tools start fresh on the next call. +# +# Usage: bash .playwright/demo-restart.sh + +set -e + +echo "Killing playwright-mcp processes..." +pkill -f "playwright-mcp" 2>/dev/null || true +pkill -f "mcp-chrome-for-testing" 2>/dev/null || true +sleep 2 + +echo "Removing Chrome lock files..." +PROFILE_DIR="$HOME/.cache/ms-playwright/mcp-chrome-for-testing-5c936c5" +rm -f "$PROFILE_DIR/SingletonLock" \ + "$PROFILE_DIR/SingletonSocket" \ + "$PROFILE_DIR/SingletonCookie" 2>/dev/null || true + +echo "Done. MCP browser will start fresh on next tool call." +echo "Now ask Claude to run: mcp__playwright__browser_run_code_unsafe filename=.playwright/pizza-demo-setup.js" diff --git a/.playwright/fresh-setup.js b/.playwright/fresh-setup.js new file mode 100644 index 00000000..f620936b --- /dev/null +++ b/.playwright/fresh-setup.js @@ -0,0 +1,43 @@ +async (page) => { + // 1. Remove extra model slots until only one remains + while (true) { + const removeBtn = await page.$('button[aria-label*="Remove Model"]'); + if (!removeBtn) break; + await removeBtn.click(); + await page.waitForTimeout(300); + } + + // 2. Open model selector and pick qwen3:8b + const modelBtn = await page.$('#model-selector-0-button'); + if (!modelBtn) return { ok: false, error: 'no model-selector-0-button' }; + await modelBtn.click(); + await page.waitForTimeout(400); + + const searchInput = await page.$('input[placeholder*="Search" i], input[placeholder*="search" i]'); + const MODEL = 'qwen3:4b'; + if (searchInput) { + await searchInput.fill(MODEL); + await page.waitForTimeout(400); + } + + const modelBtn2 = await page.$(`button:has-text("${MODEL}"), [data-value*="${MODEL}"]`); + if (modelBtn2) { + await modelBtn2.click(); + await page.waitForTimeout(400); + } + + // 3. Clear the chat-input via PM dispatch (replace with empty) + const cleared = await page.evaluate(() => { + const el = document.getElementById('chat-input'); + if (!el) return false; + const tiptap = el.editor; + if (tiptap && tiptap.view) { + const state = tiptap.view.state; + tiptap.view.dispatch(state.tr.delete(0, state.doc.content.size)); + return true; + } + return false; + }); + + return { ok: true, cleared, url: page.url() }; +} diff --git a/.playwright/inject-relay.js b/.playwright/inject-relay.js new file mode 100644 index 00000000..468d0d54 --- /dev/null +++ b/.playwright/inject-relay.js @@ -0,0 +1,28 @@ +async (page) => { + const pages = page.context().pages(); + const ontospherePage = pages.find(p => p.url().includes('docker-dev')); + if (!ontospherePage) return { ok: false, error: 'Ontosphere tab not found' }; + + const code = await ontospherePage.evaluate(async () => { + const r = await fetch('/relay-bookmarklet.js'); + let src = await r.text(); + src = src.replace(/__RELAY_URL__/g, 'http://docker-dev.iwm.fraunhofer.de:8080/relay.html'); + src = src.replace(/__RELAY_ORIGIN__/g, 'http://docker-dev.iwm.fraunhofer.de:8080'); + // Expose relay internals globally before the IIFE closes + src = src.replace(/\}\)\(\);\s*$/, [ + ' window.__vgInjectResult = injectResult;', + ' window.__vgIsStreaming = isAiStreaming;', + ' window.__vgWaitForIdle = waitForIdle;', + '})();' + ].join('\n')); + return src; + }); + + await page.addScriptTag({ content: code }); + return await page.evaluate(() => ({ + ok: true, + instanceId: window.__vgRelayInstanceId, + popup: window.__vgRelayPopup ? !window.__vgRelayPopup.closed : false, + exposed: typeof window.__vgInjectResult === 'function' + })); +} diff --git a/.playwright/owui-auth-inject.js b/.playwright/owui-auth-inject.js new file mode 100644 index 00000000..d2917cdf --- /dev/null +++ b/.playwright/owui-auth-inject.js @@ -0,0 +1,11 @@ +async (page) => { + const fs = await import('node:fs'); + const state = JSON.parse(fs.readFileSync('/home/hanke/ontosphere/.playwright/owui-auth.json', 'utf8')); + const token = state.cookies?.find(c => c.name === 'token')?.value; + if (!token) return { ok: false, error: 'no token' }; + await page.evaluate((t) => { document.cookie = `token=${t}; path=/`; }, token); + await page.reload(); + await page.waitForLoadState('networkidle'); + const loggedIn = await page.evaluate(() => !!document.cookie.includes('token')); + return { ok: loggedIn, url: page.url() }; +} diff --git a/.playwright/owui-login.js b/.playwright/owui-login.js new file mode 100644 index 00000000..64cec435 --- /dev/null +++ b/.playwright/owui-login.js @@ -0,0 +1,11 @@ +async (page) => { + await page.context().addCookies([{ + name: 'token', + value: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjRmNjQwZjYzLThmY2MtNGMxMy1iNDcyLTFhMzBjZTU5ZjAwNCIsImp0aSI6Ijc0M2JiMmQ1LWVhMTQtNDc2Yi1iMTFkLTJlZWNmMDY2NTM3OCJ9.88gzl7KSJY_S2s_xJkxXi1f_nP2gQHtrBYzlVmrdPls', + domain: 'gpuserver1-sit.iwm.fraunhofer.de', + path: '/', + }]); + await page.reload(); + await page.waitForLoadState('networkidle'); + return { url: page.url(), title: await page.title() }; +} diff --git a/.playwright/pizza-demo-setup.js b/.playwright/pizza-demo-setup.js new file mode 100644 index 00000000..d93f78ac --- /dev/null +++ b/.playwright/pizza-demo-setup.js @@ -0,0 +1,144 @@ +/** + * pizza-demo-setup.js — Bootstrap OWUI relay session for pizza ontology recording + * + * Usage: mcp__playwright__browser_run_code_unsafe filename=.playwright/pizza-demo-setup.js + * + * Prerequisites: + * - Ontosphere running at http://docker-dev.iwm.fraunhofer.de:8080 + * - OWUI tab already open + authenticated at https://gpuserver1-sit.iwm.fraunhofer.de + * + * What it does: + * 1. fresh-setup: select qwen3:4b, clear input + * 2. Navigate to OWUI home + * 3. Type the full README starter prompt (Shift+Enter for newlines) → creates /c/ URL + * The starter prompt embeds the relay format + help() call. + * The model reads it and calls help() itself — no separate INSTR injection. + * 4. Inject relay bookmarklet (relay pre-seeds the embedded help() call so it won't + * re-execute it; only the model's own help() call in its response is dispatched) + * 5. Wait for model's help() cycle to complete + * 6. Inject Socratic starter question (Turn 0) via relay + * + * After this script: relay connected, Turn 0 live. Drive with turn-driver.js. + */ + +async (page) => { + const MODEL = 'qwen3:4b'; + + const pages = page.context().pages(); + const owuiPage = pages.find(p => p.url().includes('gpuserver1-sit')); + const vgPage = pages.find(p => p.url().includes('docker-dev') && !p.url().includes('relay')); + if (!owuiPage) return { ok: false, error: 'no OWUI tab' }; + if (!vgPage) return { ok: false, error: 'no Ontosphere tab' }; + + // ── 0. Reload Ontosphere to clear any graph state from previous sessions ─── + await vgPage.reload(); + await vgPage.waitForFunction( + () => typeof window.__mcpTools?.addNode === 'function', + { timeout: 30_000 }, + ); + + // ── 1. fresh-setup ───────────────────────────────────────────────────────── + await owuiPage.goto('https://gpuserver1-sit.iwm.fraunhofer.de/'); + await owuiPage.waitForTimeout(1500); + + while (true) { + const btn = await owuiPage.$('button[aria-label*="Remove Model"]'); + if (!btn) break; + await btn.click(); + await owuiPage.waitForTimeout(300); + } + const modelBtn = await owuiPage.$('#model-selector-0-button'); + if (modelBtn) { + await modelBtn.click(); + await owuiPage.waitForTimeout(400); + const search = await owuiPage.$('input[placeholder*="Search" i]'); + if (search) { await search.fill(MODEL); await owuiPage.waitForTimeout(400); } + const pick = await owuiPage.$(`button:has-text("${MODEL}"), [data-value*="${MODEL}"]`); + if (pick) { await pick.click(); await owuiPage.waitForTimeout(400); } + } + + // ── 2. Type full README starter prompt — Shift+Enter for newlines ────────── + // Source of truth: README.md "Starter prompt" section. + // The embedded help() call (`id:0`) is pre-seeded by the relay on startup so + // it will NOT be executed. Only the model's own help() call in its response fires. + const STARTER_LINES = [ + 'You are connected to Ontosphere via a relay. A script in this tab intercepts your tool calls, runs them in Ontosphere, and injects results back as a user message. If a tool call returns success:false, read the error, fix the argument, and retry the same call immediately — never skip a failed call. Ask the user what they would like to build.', + '', + 'Output format — one JSON-RPC 2.0 call per line, backtick-wrapped:', + '`{"jsonrpc":"2.0","id":,"method":"tools/call","params":{"name":"","arguments":{...}}}`', + '', + 'Call help first to get full instructions and the tool list:', + '`{"jsonrpc":"2.0","id":0,"method":"tools/call","params":{"name":"help","arguments":{}}}`', + ]; + + const chatInput = await owuiPage.$('#chat-input'); + if (!chatInput) return { ok: false, error: 'no #chat-input' }; + await chatInput.click(); + await owuiPage.waitForTimeout(200); + + for (let i = 0; i < STARTER_LINES.length; i++) { + if (STARTER_LINES[i]) await owuiPage.keyboard.type(STARTER_LINES[i], { delay: 2 }); + if (i < STARTER_LINES.length - 1) await owuiPage.keyboard.press('Shift+Enter'); + } + await owuiPage.keyboard.press('Enter'); + await owuiPage.waitForFunction(() => location.pathname.startsWith('/c/'), { timeout: 10000 }); + const chatUrl = owuiPage.url(); + + // ── 3. Inject relay (fetch from VG tab, addScriptTag bypasses mixed-content) ─ + const relayCode = await vgPage.evaluate(async () => { + const r = await fetch('/relay-bookmarklet.js'); + let src = await r.text(); + src = src.replace(/__RELAY_URL__/g, 'http://docker-dev.iwm.fraunhofer.de:8080/relay.html'); + src = src.replace(/__RELAY_ORIGIN__/g, 'http://docker-dev.iwm.fraunhofer.de:8080'); + src = src.replace(/\}\)\(\);\s*$/, [ + ' window.__vgInjectResult = injectResult;', + ' window.__vgIsStreaming = isAiStreaming;', + ' window.__vgWaitForIdle = waitForIdle;', + ' window.__vgIsRelayIdle = function() { return callQueue.length === 0 && !isProcessing; };', + '})();', + ].join('\n')); + return src; + }); + await owuiPage.addScriptTag({ content: relayCode }); + await owuiPage.waitForTimeout(300); + + // ── 4. Wait for model's help() cycle to complete ────────────────────────── + // Model reads starter prompt → calls help() itself → relay executes → model + // reads manifest and responds. Use content-length + relay-idle (same as + // turn-driver.js) — isAiStreaming() is unreliable in newer OWUI. + let _lastLen = -1; + async function isBusy() { + const state = await owuiPage.evaluate(() => ({ + relayBusy: !(window.__vgIsRelayIdle?.() ?? true), + len: document.body.innerText?.length ?? 0, + })).catch(() => ({ relayBusy: false, len: _lastLen })); + const growing = _lastLen >= 0 && state.len !== _lastLen; + _lastLen = state.len; + return state.relayBusy || growing; + } + const helpDeadline = Date.now() + 180_000; + const pollMs = 500; + let silentMs = 0; + while (Date.now() < helpDeadline) { + const busy = await isBusy(); + if (busy) silentMs = 0; else silentMs += pollMs; + if (silentMs >= 3_000) break; + await owuiPage.waitForTimeout(pollMs); + } + await owuiPage.waitForTimeout(1000); + + // ── 5. Inject Turn 0 — Socratic starter ─────────────────────────────────── + const TURN0 = 'I want to learn OWL ontology concepts through a hands-on example. I will guide you through the pizza domain step by step — one concept at a time. Rule: for each question I ask, model exactly the concept I ask about on the canvas, then stop and wait. Do not add anything beyond what I asked. Do not arrange nodes automatically. Use the ex: prefix for all IRIs (ex: maps to http://example.org/). First question: in OWL, what is the most fundamental building block for representing a concept? Create a single Pizza class — just this one node, nothing more. Wait for my next question.'; + let turn0Ok = false; + for (let i = 0; i < 8; i++) { + turn0Ok = await owuiPage.evaluate((text) => window.__vgInjectResult?.(text) ?? false, TURN0); + if (turn0Ok !== false) break; + await owuiPage.waitForTimeout(500); + } + + await owuiPage.waitForTimeout(800); + const t0btn = await owuiPage.$('#send-message-button:not([disabled])'); + if (t0btn) await t0btn.click(); + + return { ok: true, chatUrl, turn0: turn0Ok }; +} diff --git a/.playwright/send-pizza.js b/.playwright/send-pizza.js new file mode 100644 index 00000000..7c34d549 --- /dev/null +++ b/.playwright/send-pizza.js @@ -0,0 +1,37 @@ +async (page) => { + const TASK = "Build a pizza ontology. Add these three OWL classes:\n- Pizza (IRI: http://www.pizza-ontology.com/pizza.owl#Pizza)\n- PizzaBase (IRI: http://www.pizza-ontology.com/pizza.owl#PizzaBase)\n- PizzaTopping (IRI: http://www.pizza-ontology.com/pizza.owl#PizzaTopping)\n\nAll typeIri: http://www.w3.org/2002/07/owl#Class. After all three are added, run a layered layout with spacing 200."; + + // Wait for model idle using rating buttons — more reliable than __vgIsStreaming for qwen3. + await page.waitForSelector('button[aria-label="Good Response"]', { timeout: 120000 }) + .catch(() => {}); + // Extra guard: wait for __vgIsStreaming if available + await page.waitForFunction( + () => typeof window.__vgIsStreaming !== 'function' || !window.__vgIsStreaming(), + { timeout: 30000, polling: 500 } + ).catch(() => {}); + await page.waitForTimeout(500); + + const reply = await page.evaluate(() => { + const msgs = document.querySelectorAll('[data-message-author-role="assistant"]'); + return msgs[msgs.length - 1]?.innerText?.slice(0, 400) ?? ''; + }); + console.log('[QWEN][RESPONSE]', reply.replace(/\n+/g, ' ')); + + const toolMsgs = await page.evaluate(() => { + const msgs = document.querySelectorAll('[data-message-author-role="user"]'); + return [...msgs].filter(m => m.innerText?.includes('[Ontosphere')).map(m => m.innerText?.slice(0, 200)); + }); + toolMsgs.forEach(t => console.log('[TOOL]', t.replace(/\n+/g, ' '))); + + console.log('[INJECT][TASK]', TASK.slice(0, 120)); + const injected = await page.evaluate((text) => { + if (typeof window.__vgInjectResult !== 'function') return false; + return window.__vgInjectResult(text); + }, TASK); + + await page.waitForTimeout(800); + const btn = await page.$('#send-message-button:not([disabled])'); + if (btn) await btn.click(); + + return { ok: true, injected }; +} diff --git a/.playwright/send-starter.js b/.playwright/send-starter.js new file mode 100644 index 00000000..b388ba26 --- /dev/null +++ b/.playwright/send-starter.js @@ -0,0 +1,67 @@ +async (page) => { + // Canonical starter prompt — must match README.md "Starter prompt" section (plain-text line only, no backticks). + const SEED = "You are connected to Ontosphere via a relay. A script in this tab intercepts your tool calls, runs them in Ontosphere, and injects results back as a user message. Ask the user what they would like to build."; + + // INSTR: compact format reminder + pizza task inline. + // No help() call — avoids multi-thousand-word explanation that truncates. + // "Respond with ONLY tool calls" keeps response short. + const INSTR = [ + 'RELAY FORMAT — single backtick only. ALL other formats silently ignored (no error, no response):', + '`{"jsonrpc":"2.0","id":N,"method":"tools/call","params":{"name":"TOOL_NAME","arguments":{...}}}`', + 'WRONG (silently ignored): ```json{...}``` or {"tool":"x","params":{}} or {"method":"addNode",...}', + 'Respond only with backtick-wrapped JSON-RPC 2.0 calls. No prose.', + ].join('\n'); + + // Step 1: plain seed — creates /c/ URL (no backticks = no Notes routing) + const el = await page.$('#chat-input'); + if (!el) return { ok: false, error: 'no #chat-input' }; + await el.click(); + await page.waitForTimeout(200); + await page.keyboard.type(SEED, { delay: 2 }); + console.log('[INJECT][SEED]', SEED); + await page.keyboard.press('Enter'); + await page.waitForFunction(() => location.pathname.startsWith('/c/'), { timeout: 8000 }); + const chatUrl = page.url(); + + // Step 2: inject relay with exposed internals + const pages = page.context().pages(); + const vgPage = pages.find(p => p.url().includes('docker-dev')); + if (!vgPage) return { ok: false, error: 'no Ontosphere tab', chatUrl }; + const code = await vgPage.evaluate(async () => { + const r = await fetch('/relay-bookmarklet.js'); + let src = await r.text(); + src = src.replace(/__RELAY_URL__/g, 'http://docker-dev.iwm.fraunhofer.de:8080/relay.html'); + src = src.replace(/__RELAY_ORIGIN__/g, 'http://docker-dev.iwm.fraunhofer.de:8080'); + src = src.replace(/\}\)\(\);\s*$/, [ + ' window.__vgInjectResult = injectResult;', + ' window.__vgIsStreaming = isAiStreaming;', + ' window.__vgWaitForIdle = waitForIdle;', + '})();' + ].join('\n')); + return src; + }); + await page.addScriptTag({ content: code }); + + // Step 3: wait for seed response via relay __vgIsStreaming poll + const deadline = Date.now() + 600_000; + while (Date.now() < deadline) { + const s = await page.evaluate(() => window.__vgIsStreaming?.() ?? false); + if (!s) break; + await page.waitForTimeout(1000); + } + await page.waitForTimeout(500); + console.log('[QWEN][SEED] idle'); + + // Step 4: inject INSTR+task (contains backticks — OK now we're on /c/) + console.log('[INJECT][INSTR]', INSTR.slice(0, 120)); + const injected = await page.evaluate((text) => { + if (typeof window.__vgInjectResult !== 'function') return false; + return window.__vgInjectResult(text); + }, INSTR); + + await page.waitForTimeout(800); + const btn = await page.$('#send-message-button:not([disabled])'); + if (btn) await btn.click(); + + return { ok: true, chatUrl, relayExposed: injected }; +} diff --git a/.playwright/send-task.js b/.playwright/send-task.js new file mode 100644 index 00000000..b8e2f0bd --- /dev/null +++ b/.playwright/send-task.js @@ -0,0 +1,28 @@ +async (page, TASK) => { + // Generic task injection — waits for model idle, injects TASK via relay, fallback-clicks send. + // TASK: string — the message to send. Caller sets this before passing to page.evaluate or + // when using directly: node -e "require('./.playwright/send-task.js')" (not typical). + // Typical usage from a thin wrapper: + // const sendTask = await fs.readFile('.playwright/send-task.js', 'utf8'); + // await page.evaluate(new Function('page', sendTask), TASK); + + // Wait for model idle — rating buttons are reliable for qwen3 (no false negatives during think phase) + await page.waitForSelector('button[aria-label="Good Response"]', { timeout: 120000 }).catch(() => {}); + await page.waitForFunction( + () => typeof window.__vgIsStreaming !== 'function' || !window.__vgIsStreaming(), + { timeout: 30000, polling: 500 } + ).catch(() => {}); + await page.waitForTimeout(500); + + const injected = await page.evaluate((text) => { + if (typeof window.__vgInjectResult !== 'function') return false; + return window.__vgInjectResult(text); + }, TASK); + + // doSubmit is async — fallback click if it didn't fire + await page.waitForTimeout(800); + const btn = await page.$('#send-message-button:not([disabled])'); + if (btn) await btn.click(); + + return { ok: true, injected }; +} diff --git a/.playwright/session-log-start.sh b/.playwright/session-log-start.sh new file mode 100755 index 00000000..3afaaeab --- /dev/null +++ b/.playwright/session-log-start.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Called at relay session start. Clears session.log and starts a persistent +# aggregator that tails all console-*.log files into it. +LOG_DIR="$(dirname "$0")/../.playwright-mcp" +SESSION_LOG="$LOG_DIR/session.log" + +# Kill any previous aggregator +if [ -f "$LOG_DIR/.session-log-pid" ]; then + old_pid=$(cat "$LOG_DIR/.session-log-pid") + kill "$old_pid" 2>/dev/null +fi + +# Clear log +> "$SESSION_LOG" + +# Start aggregator: watch for new console-*.log files and tail them +( + declare -A tailed + while true; do + for f in "$LOG_DIR"/console-*.log; do + [ -f "$f" ] || continue + if [ -z "${tailed[$f]+x}" ]; then + tailed["$f"]=1 + tail -F "$f" -n +1 >> "$SESSION_LOG" & + fi + done + sleep 1 + done +) & +echo $! > "$LOG_DIR/.session-log-pid" +echo "[session-log] started aggregator PID=$(cat $LOG_DIR/.session-log-pid) → $SESSION_LOG" diff --git a/.playwright/turn-driver.js b/.playwright/turn-driver.js new file mode 100644 index 00000000..f0aa39ff --- /dev/null +++ b/.playwright/turn-driver.js @@ -0,0 +1,178 @@ +/** + * turn-driver.js — Drive Socratic pizza ontology session via OWUI relay. + * + * Usage: mcp__playwright__browser_run_code_unsafe filename=.playwright/turn-driver.js + * + * Prerequisites: + * - pizza-demo-setup.js ran: relay injected, help() call sent, Turn 0 in flight or idle + * - window.__vgIsStreaming and window.__vgInjectResult exposed on OWUI tab + * + * Arc: T1–T10 demonstrating OWL concepts through Socratic questions. + * Questions guide toward OWL features without prescribing class names — + * qwen3 decides structure, we steer toward the concept to showcase. + * + * Logs full model response text after each turn for analysis. + */ + +async (page) => { + const IDLE_TIMEOUT_MS = 300_000; // 5 min per turn (reasoning can be slow) + const INJECT_RETRIES = 8; + const INJECT_RETRY_DELAY = 500; + + const pages = page.context().pages(); + const owuiPage = pages.find(p => p.url().includes('gpuserver1-sit')); + if (!owuiPage) return { ok: false, error: 'no OWUI tab' }; + + const TURNS = [ + // T1 — ingredient hierarchy: PizzaTopping + PizzaBase as INDEPENDENT classes (NOT subClassOf Pizza) + // PizzaTopping/PizzaBase must be siblings of Pizza, not children — avoids semantic leak where + // ingredients get inferred as Pizza via subclass chain or prp-range. + 'A pizza is made of two kinds of ingredient — a topping and a base. In OWL these form their own separate class hierarchies, distinct from the pizza itself. Add ex:PizzaTopping and ex:PizzaBase as independent owl:Class nodes — they are not a kind of pizza, so do not add any subClassOf edge to ex:Pizza. Then add five specific topping subclasses (each rdfs:subClassOf ex:PizzaTopping): ex:SalamiTopping, ex:HamTopping, ex:PineappleTopping, ex:MozzarellaTopping, ex:TomatoTopping. Add two base subclasses (each rdfs:subClassOf ex:PizzaBase): ex:ThinCrustBase, ex:DeepPanBase. All nodes and all subClassOf edges required. Then arrange the canvas. Wait for my next question.', + + // T2 — owl:ObjectProperty hasPart with rdfs:domain only (NO range) + // Range must be omitted — declaring range=Pizza would cause prp-range to infer that ingredients + // are pizzas, which is semantically wrong. Domain alone is sufficient for classification. + // CRITICAL: rdfs:domain — OWL-RL does NOT read owl:domain. + 'In OWL, the relationship between a pizza and its parts is an owl:ObjectProperty. Create ex:hasPart as an ObjectProperty on the canvas. Declare its domain using rdfs:domain pointing to ex:Pizza — this tells the reasoner that anything with a hasPart connection is a pizza. Do not declare a range — leaving it open keeps ingredients semantically clean. Important: use rdfs:domain, not owl:domain. Wait for my next question.', + + // T3 — named pizza subclasses: SalamiPizza, HawaiianPizza, MargheritaPizza + // Three subclasses of Pizza — laid out before the equivalentClass axioms are added. + 'There are many specific kinds of pizza. Add three named pizza classes: ex:SalamiPizza, ex:HawaiianPizza, and ex:MargheritaPizza. Each is a subclass of ex:Pizza — add all three nodes and all three rdfs:subClassOf ex:Pizza edges. Then arrange the hierarchy. Wait for my next question.', + + // T4 — Socratic: owl:equivalentClass + owl:Restriction + owl:someValuesFrom. + // Characteristic toppings: Salami → SalamiTopping, Hawaiian → PineappleTopping, + // Margherita → TomatoTopping. Must match ABox individuals added in T7/T8. + 'In OWL a class can be defined by a necessary-and-sufficient condition using owl:equivalentClass and owl:Restriction with owl:someValuesFrom. Define each named pizza class with such a condition — SalamiPizza by its characteristic SalamiTopping, HawaiianPizza by PineappleTopping, MargheritaPizza by TomatoTopping. Wait for my next question.', + + // T5 — expandNode all + runLayout + // Reveal property cards across all TBox classes before switching to ABox. + 'Expand all class nodes on the canvas to reveal their asserted properties, then arrange. Wait for my next question.', + + // T6 — ABox: setViewMode + 3 untyped NamedIndividuals + // No class assertion — prp-domain (hasPart domain Pizza) will infer Pizza type. + // cls-svf1 + cax-eqc2 will later classify each into its named pizza type. + 'Everything so far is the TBox — the schema. Switch to the individuals view (ABox) and create three pizza individuals: ex:pizza1, ex:pizza2, and ex:pizza3. Give each only the owl:NamedIndividual type — do NOT assert any pizza class (not Pizza, not SalamiPizza, nothing). Only the three bare nodes. The reasoner will classify them once we add ingredients. Arrange. Wait for my next question.', + + // T7 — build pizza1 (Salami): typed parts + hasPart connections + // SalamiTopping is the characteristic for SalamiPizza (T4 equivalentClass). + 'Build ex:pizza1 as a Salami pizza. Add three ingredient individuals to the canvas: ex:salami1 of type ex:SalamiTopping, ex:mozz1 of type ex:MozzarellaTopping, and ex:base1 of type ex:ThinCrustBase. Connect all three to ex:pizza1 using ex:hasPart. Do not assert any pizza class on ex:pizza1 — leave it untyped; the reasoner will classify it. Wait for my next question.', + + // T8 — build pizza2 (Hawaiian) + pizza3 (Margherita) + layout + // PineappleTopping is characteristic for HawaiianPizza; TomatoTopping for MargheritaPizza. + // Must NOT reuse TBox class IRIs as ABox individuals. + 'Build ex:pizza2 as a Hawaiian pizza and ex:pizza3 as a Margherita pizza. For ex:pizza2: add ex:pineapple1 of type ex:PineappleTopping, ex:ham1 of type ex:HamTopping, and ex:base2 of type ex:DeepPanBase, then connect all three to ex:pizza2 via ex:hasPart. For ex:pizza3: add ex:tom1 of type ex:TomatoTopping, ex:mozz2 of type ex:MozzarellaTopping, and ex:base3 of type ex:ThinCrustBase, then connect all three to ex:pizza3 via ex:hasPart. Do not assert any class type on pizza2 or pizza3. Reveal all node properties and arrange the canvas. Wait for my next question.', + + // T9 — runReasoning + 'The schema and all three pizzas are in place. Now apply OWL-RL reasoning to derive everything that can be inferred. Wait for my next question.', + + // T10 — classification showcase via graph query + // queryGraph covers urn:vg:data + urn:vg:inferred by default — model should reach for it + // naturally when asked to "verify" classification. getNodeDetails is acceptable fallback. + // cls-svf1: pizza1 hasPart salami1 ∧ salami1 type SalamiTopping → pizza1 type _:restriction + // cax-eqc2: _:restriction subClassOf SalamiPizza → pizza1 type SalamiPizza + 'The equivalentClass axioms we defined in the TBox should have been applied consistently across all three pizzas. Verify that the classification held: did each pizza receive the type its ingredient composition implies? Use whatever tool gives you the clearest proof — querying the graph or inspecting individual nodes. Show the evidence, state which types are inferred vs asserted, and trace the OWL-RL rule chain for at least one pizza.', + ]; + + // Reliable idle: content-length stability + relay queue drained. + // isAiStreaming() is NOT used — newer OWUI keeps send-button enabled while + // generating, causing false-idle readings. Content growth is the true signal. + let _lastLen = -1; + async function isBusy() { + const state = await owuiPage.evaluate(() => ({ + relayBusy: !(window.__vgIsRelayIdle?.() ?? true), + len: document.body.innerText?.length ?? 0, + })).catch(() => ({ relayBusy: false, len: _lastLen })); + const growing = _lastLen >= 0 && state.len !== _lastLen; + _lastLen = state.len; + return state.relayBusy || growing; + } + + async function waitIdle(stableMs = 3_000) { + const deadline = Date.now() + IDLE_TIMEOUT_MS; + const pollMs = 500; + let silentMs = 0; + while (Date.now() < deadline) { + const busy = await isBusy(); + if (busy) silentMs = 0; else silentMs += pollMs; + if (silentMs >= stableMs) return true; + await owuiPage.waitForTimeout(pollMs); + } + return false; + } + + // 10-second continuous silence before typing — resets on any activity. + async function waitQuiet(quietMs = 10_000, maxMs = 45_000) { + const pollMs = 500; + let silentMs = 0; + const deadline = Date.now() + maxMs; + while (silentMs < quietMs && Date.now() < deadline) { + const busy = await isBusy(); + if (busy) silentMs = 0; else silentMs += pollMs; + await owuiPage.waitForTimeout(pollMs); + } + } + + async function injectTurn(text) { + for (let i = 0; i < INJECT_RETRIES; i++) { + const ok = await owuiPage.evaluate( + (t) => typeof window.__vgInjectResult === 'function' ? window.__vgInjectResult(t) : false, + text, + ); + if (ok !== false) return true; + await owuiPage.waitForTimeout(INJECT_RETRY_DELAY); + } + return false; + } + + // Read the last assistant message text from the OWUI DOM for response analysis. + async function captureLastResponse() { + return owuiPage.evaluate(() => { + // OWUI marks assistant messages with data-message-role or a class like 'assistant' + const byRole = document.querySelectorAll('[data-message-role="assistant"]'); + if (byRole.length) return byRole[byRole.length - 1].innerText || ''; + // Fallback: last .message.assistant or similar + const byClass = document.querySelectorAll('.message.assistant, [class*="assistant"]'); + if (byClass.length) return byClass[byClass.length - 1].innerText || ''; + return ''; + }); + } + + const results = []; + + for (let i = 0; i < TURNS.length; i++) { + const turnNum = i + 1; + + // Wait until truly idle (3s stability), then 10s of continuous silence + // before typing to ensure relay queue is drained and model isn't mid-think. + const idleReached = await waitIdle(); + await waitQuiet(); + await owuiPage.waitForTimeout(600); + + // Capture previous response before injecting next turn + const prevResponse = await captureLastResponse(); + if (prevResponse) { + console.log(`[TURN-DRIVER] T${turnNum - 1} model response:\n---\n${prevResponse.slice(0, 2000)}\n---`); + } + + const injected = await injectTurn(TURNS[i]); + await owuiPage.waitForTimeout(800); + + const btn = await owuiPage.$('#send-message-button:not([disabled])'); + if (btn) await btn.click(); + + results.push({ turn: turnNum, injected, idleReached }); + console.log(`[TURN-DRIVER] T${turnNum} sent — idle=${idleReached} injected=${injected}`); + + await owuiPage.waitForTimeout(1000); + } + + // Capture final response after last turn finishes + await waitIdle(); + await owuiPage.waitForTimeout(600); + const finalResponse = await captureLastResponse(); + if (finalResponse) { + console.log(`[TURN-DRIVER] T${TURNS.length} model response:\n---\n${finalResponse.slice(0, 2000)}\n---`); + } + + return { ok: true, turns: results }; +} diff --git a/CLAUDE.md b/CLAUDE.md index b0daff87..85a8100f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,3 +7,21 @@ After adding or changing any user-facing feature, always check whether `README.m ## Demo videos See [docs/demo-scripts/HOWTO.md](docs/demo-scripts/HOWTO.md) when recording or creating demo videos. + +## OWUI relay sessions + +See [docs/owui-relay-session.md](docs/owui-relay-session.md) for the full session setup guide (MCP browser tabs, auth, startup sequence, format enforcement, debug checklist). + +## Background task output + +Never write background task output to `/tmp`. Always tee to `logs/` in the repo: + +```bash +some-long-running-command 2>&1 | tee logs/.log +``` + +Use a fixed, descriptive name (not timestamped) so the file is easy to find and `tail -f` works naturally. The `logs/` directory is git-ignored. Check `logs/` before `/tmp` when looking for recent run output. + +## Documented Solutions + +`docs/solutions/` — solutions to past problems (bugs, architecture patterns, workflow practices), organized by category with YAML frontmatter (`module`, `tags`, `problem_type`). Relevant when implementing or debugging in documented areas. diff --git a/README.md b/README.md index 62a94a77..5256a225 100644 --- a/README.md +++ b/README.md @@ -114,12 +114,14 @@ The API key is sent only with the RDF fetch request. CORS: the server must allow ### Ontology pre-loading -| Parameter | Alias | Description | -|-------------|--------------|-------------| -| `ontology` | `ontologies` | Comma-separated list of ontologies to load on startup, in addition to any configured autoload and `owl:imports` discovery. Each value is either a well-known short name (see table below) or an arbitrary HTTPS/HTTP URI. | +| Parameter | Description | +|--------------|-------------| +| `ontologies` | Comma-separated list of ontologies that **replaces** the configured autoload list entirely. Each value is a well-known short name (see table below) or a full HTTPS/HTTP URI. Use `?ontologies=owl,rdf,rdfs` to load only the W3C core vocabs. | +| `ontology` | Comma-separated list of ontologies to load **in addition to** the configured autoload list. | ```text -?ontology=bfo,dcat +?ontologies=owl,rdf,rdfs # replace defaults — load only W3C core vocabs +?ontology=bfo,dcat # add on top of configured autoload list ?ontology=bfo2020,https://example.org/myontology.ttl ``` @@ -476,7 +478,7 @@ The **AI Relay Bridge** connects any AI chat tab to Ontosphere with no server, e **Starter prompt** (paste into your AI chat after clicking the bookmarklet): ```text -You are connected to Ontosphere via a relay. A script in this tab intercepts your tool calls, runs them in Ontosphere, and injects results back as a user message. +You are connected to Ontosphere via a relay. A script in this tab intercepts your tool calls, runs them in Ontosphere, and injects results back as a user message. If a tool call returns success:false, read the error, fix the argument, and retry the same call immediately — never skip a failed call. Ask the user what they would like to build. Output format — one JSON-RPC 2.0 call per line, backtick-wrapped: `{"jsonrpc":"2.0","id":,"method":"tools/call","params":{"name":"","arguments":{...}}}` diff --git a/docs/demo-scripts/HOWTO.md b/docs/demo-scripts/HOWTO.md index bffe4404..525b8298 100644 --- a/docs/demo-scripts/HOWTO.md +++ b/docs/demo-scripts/HOWTO.md @@ -55,6 +55,124 @@ for (const turn of turns) { | `clearCaption()` | Remove overlay | | `captionPause(text, ms)` | Show caption → pause → clear | +## Building an OWUI Socratic demo: interactive-first workflow + +The OWUI Socratic demo (pizza-ontology) drives a live AI model through Socratic questions +and records its tool calls on the Ontosphere canvas. The scripted Playwright spec must +match what that specific model (qwen3:4b) actually does — not what we wish it did. + +**Workflow: validate questions interactively, then encode them in the spec.** + +### Step 1 — Prerequisites + +```bash +# Terminal 1: keep Ontosphere dev server running +npm run dev + +# Terminal 2: clear stale browser state before each session +bash .playwright/demo-restart.sh +``` + +MCP browser must have two tabs **before** running setup: +- **Tab 0** — Ontosphere at `http://docker-dev.iwm.fraunhofer.de:8080` +- **Tab 1** — OWUI at `https://gpuserver1-sit.iwm.fraunhofer.de` (authenticated) + +If OWUI shows auth page, restore session from saved state: +```bash +OWUI_URL=https://gpuserver1-sit.iwm.fraunhofer.de npm run demo:owui:auth +``` +Or inject the token inline (see pizza-demo-setup.js auth section). + +### Step 2 — Bootstrap the interactive session + +```text +mcp__playwright__browser_run_code_unsafe filename=.playwright/pizza-demo-setup.js +``` + +Expected return: `{ ok: true, chatUrl, instrInjected: true, turn0: true }`. + +This sends: plain-text seed → relay injection → `help({})` call → T0 question. +Turn 0 is now in flight. Wait for the model to finish (relay toast appears, streaming stops). + +### Step 3 — Observe T0 behavior + +Check two things: +1. **Canvas**: take a screenshot or read node list — does it match the expected OWL concept? +2. **OWUI chat body**: read `document.body.innerText` to see what qwen3 actually generated. + Look for which tools were called (relay result lines: `[Ontosphere — N tools ✓]`). + +Key questions per turn: +- Did qwen3 use the right OWL concept? (e.g. `rdfs:subClassOf`, `owl:disjointWith`) +- Did it stay on-topic? (qwen3 sometimes introduces off-topic classes like `Person/Alice`) +- Did it call too many tools in one response (went ahead of where we wanted to stop)? +- Did the canvas change correctly? + +### Step 4 — Drive turns interactively + +Inject remaining turns one at a time via turn-driver.js OR manually via `__vgInjectResult`: + +```text +mcp__playwright__browser_run_code_unsafe filename=.playwright/turn-driver.js +``` + +Or inject a single turn manually: +```js +// In browser_run_code_unsafe: +async (page) => { + const owuiPage = page.context().pages().find(p => p.url().includes('gpuserver1-sit')); + const ok = await owuiPage.evaluate(t => window.__vgInjectResult?.(t), 'Your question here'); + const btn = await owuiPage.$('#send-message-button:not([disabled])'); + if (btn) await btn.click(); + return { ok }; +} +``` + +### Step 5 — Capture turn-by-turn results + +After each turn completes (streaming=false), read the model response: + +```js +async (page) => { + const owuiPage = page.context().pages().find(p => p.url().includes('gpuserver1-sit')); + return owuiPage.evaluate(() => { + const texts = Array.from(document.querySelectorAll('[class*="prose"],[class*="markdown"]')) + .map(e => e.innerText).filter(t => t.length > 20); + return texts[texts.length - 1]?.slice(0, 3000); + }); +} +``` + +And check relay results in the OWUI chat for `[Ontosphere — N tools ✓]` lines. + +### Step 6 — Iterate questions + +If qwen3 goes off-topic, uses wrong format, or skips a concept: +- Adjust the question in `.playwright/turn-driver.js` (TURNS array) +- Add a **fallback nudge** (see `pizza-ontology.md` Fallback nudges section) +- Re-run from Step 2 with a fresh session (`demo-restart.sh` first) + +Common failure modes: + +| Failure | Cause | Fix | +|---------|-------|-----| +| Goes ahead of the question | Model is eager/comprehensive | Add "only do this one step" | +| Off-topic nodes (Person/Alice) | Hallucinated from training | Add "stay within the pizza domain" | +| Wrong format (triple-backtick) | Model forgot relay format | Nudge with `help({tool:"X"})` reminder | +| subClassOf reversed | Confuses direction | Nudge: "points from specific to general" | + +### Step 7 — Promote validated questions to scripted spec + +Once all T0–T9 questions produce the correct OWL concepts in order: +1. Copy the final TURNS array from `turn-driver.js` → `e2e/demo-openwebui-socratic.spec.ts` +2. Update caption text in `pizza-ontology.md` to match what qwen3 actually produced +3. Run `npm run demo:owui:video` to record the scripted version + +The scripted spec replays the same questions against a live model session — it is not +pre-recorded; the model still runs live. The spec just drives the questions and waits for +tool-call completion at each turn. + +--- + ## Planned videos - `advert-intro` — done (relay demo, mock chat + app side by side) diff --git a/docs/demo-scripts/pizza-ontology.md b/docs/demo-scripts/pizza-ontology.md new file mode 100644 index 00000000..cff5a807 --- /dev/null +++ b/docs/demo-scripts/pizza-ontology.md @@ -0,0 +1,232 @@ +# Demo Script: Pizza Ontology — Socratic AI Teaching Session + +A small local AI model (qwen3:4b) is guided to discover and build a pizza ontology +through Socratic questions. The model never receives direct instructions — only leading +questions that lead it to apply the right OWL concepts and relay tool calls. + +--- + +## Two ways to reproduce + +| Method | When to use | +|--------|-------------| +| [**A. Automated Playwright spec**](#a-automated-playwright-spec) | Clean reproducible recording via `npm run demo:owui:video` | +| [**B. Interactive MCP session**](#b-interactive-mcp-session) | Live exploration / debugging / one-off demos | + +--- + +## A. Automated Playwright spec + +Produces a side-by-side video (OWUI chat left, Ontosphere canvas right) with caption +overlays explaining each turn. Output: `docs/demo-videos/openwebui-socratic.mp4`. + +### Prerequisites + +```bash +# 1. OWUI authentication — do once, saves .playwright/owui-auth.json +OWUI_URL=https://gpuserver1-sit.iwm.fraunhofer.de npm run demo:owui:auth + +# 2. Ontosphere dev server running +npm run dev +``` + +### Record + +```bash +OWUI_URL=https://gpuserver1-sit.iwm.fraunhofer.de npm run demo:owui:video +``` + +This runs `e2e/demo-openwebui-socratic.spec.ts` inside Xvfb (headless with a virtual +display), records at 1920×1080, then converts to MP4 via ffmpeg. + +### What the spec does + +1. Opens `demo-stage-owui.html` — OWUI on the left, Ontosphere on the right +2. Selects `qwen3:4b`, navigates to a fresh chat +3. Sends plain-text seed → establishes `/c/` URL +4. Injects relay bookmarklet (fetched from Ontosphere frame, bypasses mixed-content) +5. Sends bare `help({})` call — relay executes it, model reads full manifest from result +6. Runs T0–T9 with `__vgIsStreaming` idle detection between turns +7. Shows before/after caption overlays at each turn + +### Caption overlay text (per turn) + +| Turn | Before caption | After caption | +|------|---------------|---------------| +| T0 | Asking qwen3: what is a pizza in OWL terms? | Root Pizza class on canvas. Next: its building blocks. | +| T1 | Guide: model sub-categories and arrange the hierarchy. | rdfs:subClassOf edges + layout applied. Next: mutual exclusion. | +| T2 | Guide: declare building blocks mutually exclusive in OWL. | owl:disjointWith asserted. Next: deepen the hierarchy. | +| T3 | Guide: add concrete varieties under each building block. | Third hierarchy level + layout. Next: object property. | +| T4 | Guide: express composition with a formal object property. | owl:ObjectProperty with domain + range. Next: expand node. | +| T5 | Guide: reveal the Pizza class details on canvas. | expandNode shows all properties. Next: ABox individuals. | +| T6 | Guide: switch to individuals view and create a real pizza. | owl:NamedIndividual in ABox view. Next: connect its parts. | +| T7 | Guide: add parts and connect via the object property. | Individual linked to toppings + base. Next: reasoning. | +| T8 | Guide: trigger OWL-RL reasoning. | Reasoner materialised inferred triples. Next: inspect results. | +| T9 | Guide: inspect what the reasoner inferred about the individual. | Pizza ontology complete — built through Socratic questioning. | + +--- + +## B. Interactive MCP session + +Drive the session manually via Claude Code + Playwright MCP browser. +Use this for live demos, debugging, or exploring model behaviour. + +### Prerequisites + +1. MCP browser open with two tabs: + - **Tab 0** — Ontosphere at `http://docker-dev.iwm.fraunhofer.de:8080` + - **Tab 1** — OWUI at `https://gpuserver1-sit.iwm.fraunhofer.de` (authenticated) + +2. Start aggregated session log: + +```bash +kill $(cat /tmp/session-log-agg.pid 2>/dev/null) 2>/dev/null; true +while true; do cat .playwright-mcp/console-*.log 2>/dev/null > .playwright-mcp/session.log; sleep 2; done & +echo $! > /tmp/session-log-agg.pid +``` + +### Bootstrap + +``` +mcp__playwright__browser_run_code_unsafe filename=.playwright/pizza-demo-setup.js +``` + +Returns `{ ok: true, chatUrl, instrInjected, turn0 }`. Verify all truthy. + +**What this does:** +1. Selects qwen3:4b, clears input +2. Sends plain-text seed → establishes `/c/` URL +3. Injects relay bookmarklet (cross-tab fetch from Ontosphere tab, bypasses HTTPS mixed-content) +4. Waits for seed response to finish +5. Sends format INSTR (single-backtick JSON-RPC 2.0 only, key tool signatures, pizza IRIs) +6. Waits for INSTR response + 1s for `injectInProgress` flag to reset +7. Injects Turn 0: "Can you teach me how ontologies work…" + +### Drive turns T1–T6 + +After Turn 0 completes (relay toast visible, model idle): + +``` +mcp__playwright__browser_run_code_unsafe filename=.playwright/turn-driver.js +``` + +Injects T1–T6 sequentially. Waits up to 3 min per turn for model to finish. + +### Monitor while running + +```bash +tail -f .playwright-mcp/session.log | grep -E '\[RelayBridge\] Tool result:|Unknown tool:' +``` + +Key log lines: +- `[RelayBridge] Tool result: addNode {"success":true,...}` — tool executed +- `[RelayBridge] Unknown tool: X` — qwen3 used wrong tool name +- `[RelayBridge] BC message received: {"type":"vg-call",...}` — relay received call + +--- + +## Turn-by-turn reference + +Questions guide toward OWL **concepts** — not specific class names. qwen3 decides the +ontology structure; we accept whatever it creates as long as the right OWL construct +appears. Expected entries list the concept to look for, not exact IRIs. + +--- + +### Turn 0 — Root class (in pizza-demo-setup.js) +> "I want to learn OWL ontology concepts through a hands-on example. I will guide you through the pizza domain step by step — one concept at a time. Rule: for each question I ask, model exactly the concept I ask about on the canvas, then stop and wait. Do not add anything beyond what I asked. Do not arrange nodes automatically. Use the ex: prefix for all IRIs (ex: maps to http://example.org/). First question: in OWL, what is the most fundamental building block for representing a concept? Create a single Pizza class — just this one node, nothing more. Wait for my next question." + +**Concept:** `addNode` with `typeIri=owl:Class` for a single `ex:Pizza` class. +"Use the ex: prefix" prevents qwen3 from anchoring on pizza.owl# training data IRIs. +"just this one node, nothing more" prevents qwen3 from pre-emptively adding the full 3-class hierarchy. + +--- + +### Turn 1 — Hierarchy + layout +> "A pizza is made from two distinct building blocks — a base and a topping. In OWL the predicate rdfs:subClassOf places a class beneath its parent. Add exactly two sub-class edges: one from the base class up to Pizza, one from the topping class up to Pizza. No other triples. Keep using the ex: prefix. Then arrange the hierarchy. Wait for my next question." + +**Concept:** `rdfs:subClassOf` edges from two new classes to Pizza, then `runLayout`. +Accept any names (Base/Topping, Crust/Ingredient, etc.). +Explicit direction ("from … up to Pizza") prevents reversed edges. "No other triples" prevents extra unsolicited nodes. "Keep using the ex: prefix" counters pizza.owl# IRI leakage. + +--- + +### Turn 2 — owl:disjointWith +> "In OWL, classes can be declared mutually exclusive — no individual can belong to both at the same time. Should the two building blocks of a pizza be disjoint from each other? If so, express that relationship on the canvas. Wait for my next question." + +**Concept:** `addTriple` with `predicateIri=owl:disjointWith` between the two sibling classes. + +--- + +### Turn 3 — Deeper hierarchy + layout +> "Good. Each building block has concrete varieties — for example a dough might be thin-crust or thick-crust. Add two specific sub-types under each building block, then arrange the hierarchy. Wait for my next question." + +**Concept:** More `rdfs:subClassOf` edges creating a third level, followed by `runLayout`. + +--- + +### Turn 4 — owl:ObjectProperty with domain + range +> "In OWL, composition is modelled with an owl:ObjectProperty — a named relationship that is itself a first-class node in the ontology, not just an edge. Create an object property called hasPart and declare its domain as Pizza and its range as its two building blocks. Add it to the canvas now. Wait for my next question." + +**Concept:** `addNode` with `typeIri=owl:ObjectProperty`, then `addTriple` for `rdfs:domain` and `rdfs:range`. +Naming `hasPart` explicitly prevents the model from deferring the property creation to T7 (observed failure mode: model answers in prose at T4 and only creates hasPart when it needs it at T7). + +--- + +### Turn 5 — expandNode +> "Expand the Pizza class node on the canvas so I can see all its asserted properties. Wait for my next question." + +**Concept:** `expandNode(iri=)` — reveals annotation property cards on the node. + +--- + +### Turn 6 — ABox individual +> "Everything so far is the schema — the TBox. I want to see a real pizza instance. In OWL, concrete instances are called Named Individuals. Switch to the individuals view and add one. Wait for my next question." + +**Concept:** `setViewMode({mode:"abox"})` then `addNode` with `typeIri=owl:NamedIndividual`. +"Named Individuals" in the question steers qwen3 toward the correct typeIri without naming the tool. + +--- + +### Turn 7 — Connect individual to parts +> "Give your pizza individual one individual topping and one individual base. Connect each part to the pizza individual using only the hasPart object property you defined earlier — no other properties. Wait for my next question." + +**Concept:** `addNode` for part individuals + `addTriple` using the hasPart object property from T4. +"pizza individual" (not "Pizza class") prevents triples from the wrong subject. "only the hasPart object property" prevents owl:part/owl:partOf substitutions. "no other properties" prevents extra unsolicited triples. + +--- + +### Turn 8 — OWL-RL reasoning +> "The schema and data are in place. Now apply OWL-RL reasoning to derive everything that can be inferred. Wait for my next question." + +**Concept:** `runReasoning({})`. + +--- + +### Turn 9 — Inspect inferred facts +> "What did the reasoner infer about your pizza individual? Fetch its details from the graph and report which types are now attached to it." + +**Concept:** `getNodeDetails(iri=)` — returns both asserted and inferred triples (inferred marked `inferred:true`). +"Fetch its details from the graph" signals a data retrieval action, not an answer from prior context. + +--- + +## Fallback nudges + +**Wrong format** (prose, triple-backtick, native tool syntax): +``` +Single backtick JSON-RPC 2.0 only — ALL other formats silently ignored. +`{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"help","arguments":{}}}` +Call help({}) to review the format and tool list. +``` + +**Wrong tool name** (`setLayout`, `hierarchical`, `addLink`, etc.): +``` +Tool is runLayout (not setLayout). Use addTriple (not addLink). Call help({}) for the full tool list. +``` + +**subClassOf direction reversed** (child subClassOf parent missing): +``` +rdfs:subClassOf points from the more specific class to the more general one. +Example: PizzaBase subClassOf Pizza — not Pizza subClassOf PizzaBase. +``` diff --git a/docs/demo-videos/advert-intro.mp4 b/docs/demo-videos/advert-intro.mp4 index 10bc3969..4ccd5088 100644 Binary files a/docs/demo-videos/advert-intro.mp4 and b/docs/demo-videos/advert-intro.mp4 differ diff --git a/docs/demo-videos/advert-intro.webm b/docs/demo-videos/advert-intro.webm index bb49bbad..591d59c8 100644 Binary files a/docs/demo-videos/advert-intro.webm and b/docs/demo-videos/advert-intro.webm differ diff --git a/docs/demo-videos/foaf-social-network.mp4 b/docs/demo-videos/foaf-social-network.mp4 index 8e0aad3c..b3a36c9f 100644 Binary files a/docs/demo-videos/foaf-social-network.mp4 and b/docs/demo-videos/foaf-social-network.mp4 differ diff --git a/docs/demo-videos/foaf-social-network.webm b/docs/demo-videos/foaf-social-network.webm index 5119477f..09ca80d1 100644 Binary files a/docs/demo-videos/foaf-social-network.webm and b/docs/demo-videos/foaf-social-network.webm differ diff --git a/docs/demo-videos/pizza-tutorial-chat.mp4 b/docs/demo-videos/pizza-tutorial-chat.mp4 index 7874eab0..eab34c1d 100644 Binary files a/docs/demo-videos/pizza-tutorial-chat.mp4 and b/docs/demo-videos/pizza-tutorial-chat.mp4 differ diff --git a/docs/demo-videos/pizza-tutorial-chat.webm b/docs/demo-videos/pizza-tutorial-chat.webm index d0f0c1ff..7a2775b3 100644 Binary files a/docs/demo-videos/pizza-tutorial-chat.webm and b/docs/demo-videos/pizza-tutorial-chat.webm differ diff --git a/docs/demo-videos/pizza-tutorial.mp4 b/docs/demo-videos/pizza-tutorial.mp4 index 67163517..4acb68a0 100644 Binary files a/docs/demo-videos/pizza-tutorial.mp4 and b/docs/demo-videos/pizza-tutorial.mp4 differ diff --git a/docs/demo-videos/pizza-tutorial.webm b/docs/demo-videos/pizza-tutorial.webm index 96efa927..9de44a26 100644 Binary files a/docs/demo-videos/pizza-tutorial.webm and b/docs/demo-videos/pizza-tutorial.webm differ diff --git a/docs/demo-videos/reasoning-demo.mp4 b/docs/demo-videos/reasoning-demo.mp4 index b9b55e03..41b65ad5 100644 Binary files a/docs/demo-videos/reasoning-demo.mp4 and b/docs/demo-videos/reasoning-demo.mp4 differ diff --git a/docs/demo-videos/reasoning-demo.webm b/docs/demo-videos/reasoning-demo.webm index cdf9f36c..1da0ddb3 100644 Binary files a/docs/demo-videos/reasoning-demo.webm and b/docs/demo-videos/reasoning-demo.webm differ diff --git a/docs/demo-videos/scene-ontology.mp4 b/docs/demo-videos/scene-ontology.mp4 index ce91be73..c8ff425a 100644 Binary files a/docs/demo-videos/scene-ontology.mp4 and b/docs/demo-videos/scene-ontology.mp4 differ diff --git a/docs/demo-videos/scene-ontology.webm b/docs/demo-videos/scene-ontology.webm index 0e78dc22..8764eafb 100644 Binary files a/docs/demo-videos/scene-ontology.webm and b/docs/demo-videos/scene-ontology.webm differ diff --git a/docs/mcp-demo/foaf-social-network.md b/docs/mcp-demo/foaf-social-network.md index 2317d7ea..65609f5b 100644 --- a/docs/mcp-demo/foaf-social-network.md +++ b/docs/mcp-demo/foaf-social-network.md @@ -25,12 +25,12 @@ every node and axiom will be visible on canvas. `{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://xmlns.com/foaf/0.1/Organization","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"foaf:Organization"}}}` `{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://xmlns.com/foaf/0.1/knows","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"foaf:knows"}}}` `{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://xmlns.com/foaf/0.1/member","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"foaf:member"}}}` -`{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/Person","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://xmlns.com/foaf/0.1/Agent"}}}` -`{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/Organization","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://xmlns.com/foaf/0.1/Agent"}}}` -`{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/knows","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://xmlns.com/foaf/0.1/Person"}}}` -`{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/knows","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://xmlns.com/foaf/0.1/Person"}}}` -`{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/member","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://xmlns.com/foaf/0.1/Agent"}}}` -`{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/member","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://xmlns.com/foaf/0.1/Organization"}}}` +`{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/Person","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://xmlns.com/foaf/0.1/Agent"}}}` +`{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/Organization","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://xmlns.com/foaf/0.1/Agent"}}}` +`{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/knows","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://xmlns.com/foaf/0.1/Person"}}}` +`{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/knows","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://xmlns.com/foaf/0.1/Person"}}}` +`{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/member","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://xmlns.com/foaf/0.1/Agent"}}}` +`{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/member","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://xmlns.com/foaf/0.1/Organization"}}}` ```tool-result [Ontosphere — 12 tools ✓] @@ -40,12 +40,12 @@ every node and axiom will be visible on canvas. ✓ addNode: http://xmlns.com/foaf/0.1/Organization ✓ addNode: http://xmlns.com/foaf/0.1/knows ✓ addNode: http://xmlns.com/foaf/0.1/member -✓ addLink: s=Person p=subClassOf o=Agent -✓ addLink: s=Organization p=subClassOf o=Agent -✓ addLink: s=knows p=domain o=Person -✓ addLink: s=knows p=range o=Person -✓ addLink: s=member p=domain o=Agent -✓ addLink: s=member p=range o=Organization +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple Canvas: 5 nodes, 6 links ``` @@ -69,17 +69,17 @@ Canvas: 5 nodes, 6 links `{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.org/ex#manages","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"manages"}}}` `{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.org/ex#reportsTo","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"reportsTo"}}}` `{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.org/ex#collaboratesWith","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"collaboratesWith"}}}` -`{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#Employee","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://xmlns.com/foaf/0.1/Person"}}}` -`{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#Manager","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.org/ex#Employee"}}}` -`{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#Executive","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.org/ex#Manager"}}}` -`{"jsonrpc":"2.0","id":13,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#Department","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://xmlns.com/foaf/0.1/Organization"}}}` -`{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#manages","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.org/ex#Manager"}}}` -`{"jsonrpc":"2.0","id":15,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#manages","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.org/ex#Employee"}}}` -`{"jsonrpc":"2.0","id":16,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#reportsTo","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.org/ex#Employee"}}}` -`{"jsonrpc":"2.0","id":17,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#reportsTo","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.org/ex#Manager"}}}` -`{"jsonrpc":"2.0","id":18,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#collaboratesWith","predicateIri":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","objectIri":"http://www.w3.org/2002/07/owl#SymmetricProperty"}}}` -`{"jsonrpc":"2.0","id":19,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#collaboratesWith","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.org/ex#Employee"}}}` -`{"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#collaboratesWith","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.org/ex#Employee"}}}` +`{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#Employee","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://xmlns.com/foaf/0.1/Person"}}}` +`{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#Manager","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.org/ex#Employee"}}}` +`{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#Executive","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.org/ex#Manager"}}}` +`{"jsonrpc":"2.0","id":13,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#Department","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://xmlns.com/foaf/0.1/Organization"}}}` +`{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#manages","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.org/ex#Manager"}}}` +`{"jsonrpc":"2.0","id":15,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#manages","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.org/ex#Employee"}}}` +`{"jsonrpc":"2.0","id":16,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#reportsTo","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.org/ex#Employee"}}}` +`{"jsonrpc":"2.0","id":17,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#reportsTo","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.org/ex#Manager"}}}` +`{"jsonrpc":"2.0","id":18,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#collaboratesWith","predicateIri":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","objectIri":"http://www.w3.org/2002/07/owl#SymmetricProperty"}}}` +`{"jsonrpc":"2.0","id":19,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#collaboratesWith","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.org/ex#Employee"}}}` +`{"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#collaboratesWith","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.org/ex#Employee"}}}` `{"jsonrpc":"2.0","id":21,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":200}}}` `{"jsonrpc":"2.0","id":22,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":23,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":200}}}` @@ -93,17 +93,17 @@ Canvas: 5 nodes, 6 links ✓ addNode: http://example.org/ex#manages ✓ addNode: http://example.org/ex#reportsTo ✓ addNode: http://example.org/ex#collaboratesWith -✓ addLink: s=Employee p=subClassOf o=Person -✓ addLink: s=Manager p=subClassOf o=Employee -✓ addLink: s=Executive p=subClassOf o=Manager -✓ addLink: s=Department p=subClassOf o=Organization -✓ addLink: s=manages p=domain o=Manager -✓ addLink: s=manages p=range o=Employee -✓ addLink: s=reportsTo p=domain o=Employee -✓ addLink: s=reportsTo p=range o=Manager -✓ addLink: s=collaboratesWith p=type o=SymmetricProperty -✓ addLink: s=collaboratesWith p=domain o=Employee -✓ addLink: s=collaboratesWith p=range o=Employee +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple ✓ runLayout: elk-layered ✓ expandNode ✓ runLayout: elk-layered @@ -165,44 +165,44 @@ Canvas: 8 nodes, 0 links
19 tool calls ✓ -`{"jsonrpc":"2.0","id":33,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/alice","predicateIri":"http://example.org/ex#manages","objectIri":"http://example.org/bob"}}}` -`{"jsonrpc":"2.0","id":34,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/alice","predicateIri":"http://example.org/ex#manages","objectIri":"http://example.org/frank"}}}` -`{"jsonrpc":"2.0","id":35,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/bob","predicateIri":"http://example.org/ex#manages","objectIri":"http://example.org/carol"}}}` -`{"jsonrpc":"2.0","id":36,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/bob","predicateIri":"http://example.org/ex#manages","objectIri":"http://example.org/dave"}}}` -`{"jsonrpc":"2.0","id":37,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/eve","predicateIri":"http://example.org/ex#reportsTo","objectIri":"http://example.org/alice"}}}` -`{"jsonrpc":"2.0","id":38,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/carol","predicateIri":"http://example.org/ex#reportsTo","objectIri":"http://example.org/bob"}}}` -`{"jsonrpc":"2.0","id":39,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/dave","predicateIri":"http://example.org/ex#reportsTo","objectIri":"http://example.org/bob"}}}` -`{"jsonrpc":"2.0","id":40,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/dave","predicateIri":"http://example.org/ex#collaboratesWith","objectIri":"http://example.org/eve"}}}` -`{"jsonrpc":"2.0","id":41,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/alice","predicateIri":"http://xmlns.com/foaf/0.1/knows","objectIri":"http://example.org/eve"}}}` -`{"jsonrpc":"2.0","id":42,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/bob","predicateIri":"http://xmlns.com/foaf/0.1/knows","objectIri":"http://example.org/carol"}}}` -`{"jsonrpc":"2.0","id":43,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/alice","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/engineering"}}}` -`{"jsonrpc":"2.0","id":44,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/bob","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/engineering"}}}` -`{"jsonrpc":"2.0","id":45,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/carol","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/engineering"}}}` -`{"jsonrpc":"2.0","id":46,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/dave","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/research"}}}` -`{"jsonrpc":"2.0","id":47,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/eve","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/research"}}}` -`{"jsonrpc":"2.0","id":48,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/frank","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/research"}}}` +`{"jsonrpc":"2.0","id":33,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/alice","predicateIri":"http://example.org/ex#manages","objectIri":"http://example.org/bob"}}}` +`{"jsonrpc":"2.0","id":34,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/alice","predicateIri":"http://example.org/ex#manages","objectIri":"http://example.org/frank"}}}` +`{"jsonrpc":"2.0","id":35,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/bob","predicateIri":"http://example.org/ex#manages","objectIri":"http://example.org/carol"}}}` +`{"jsonrpc":"2.0","id":36,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/bob","predicateIri":"http://example.org/ex#manages","objectIri":"http://example.org/dave"}}}` +`{"jsonrpc":"2.0","id":37,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/eve","predicateIri":"http://example.org/ex#reportsTo","objectIri":"http://example.org/alice"}}}` +`{"jsonrpc":"2.0","id":38,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/carol","predicateIri":"http://example.org/ex#reportsTo","objectIri":"http://example.org/bob"}}}` +`{"jsonrpc":"2.0","id":39,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/dave","predicateIri":"http://example.org/ex#reportsTo","objectIri":"http://example.org/bob"}}}` +`{"jsonrpc":"2.0","id":40,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/dave","predicateIri":"http://example.org/ex#collaboratesWith","objectIri":"http://example.org/eve"}}}` +`{"jsonrpc":"2.0","id":41,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/alice","predicateIri":"http://xmlns.com/foaf/0.1/knows","objectIri":"http://example.org/eve"}}}` +`{"jsonrpc":"2.0","id":42,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/bob","predicateIri":"http://xmlns.com/foaf/0.1/knows","objectIri":"http://example.org/carol"}}}` +`{"jsonrpc":"2.0","id":43,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/alice","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/engineering"}}}` +`{"jsonrpc":"2.0","id":44,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/bob","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/engineering"}}}` +`{"jsonrpc":"2.0","id":45,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/carol","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/engineering"}}}` +`{"jsonrpc":"2.0","id":46,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/dave","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/research"}}}` +`{"jsonrpc":"2.0","id":47,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/eve","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/research"}}}` +`{"jsonrpc":"2.0","id":48,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/frank","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/research"}}}` `{"jsonrpc":"2.0","id":49,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":200}}}` `{"jsonrpc":"2.0","id":50,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":51,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":200}}}` ```tool-result [Ontosphere — 19 tools ✓] -✓ addLink: s=alice p=manages o=bob -✓ addLink: s=alice p=manages o=frank -✓ addLink: s=bob p=manages o=carol -✓ addLink: s=bob p=manages o=dave -✓ addLink: s=eve p=reportsTo o=alice -✓ addLink: s=carol p=reportsTo o=bob -✓ addLink: s=dave p=reportsTo o=bob -✓ addLink: s=dave p=collaboratesWith o=eve -✓ addLink: s=alice p=knows o=eve -✓ addLink: s=bob p=knows o=carol -✓ addLink: s=alice p=member o=engineering -✓ addLink: s=bob p=member o=engineering -✓ addLink: s=carol p=member o=engineering -✓ addLink: s=dave p=member o=research -✓ addLink: s=eve p=member o=research -✓ addLink: s=frank p=member o=research +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple ✓ runLayout: dagre-lr ✓ expandNode ✓ runLayout: dagre-lr @@ -296,11 +296,11 @@ Canvas: 8 nodes, 17 links @prefix rdfs: . @prefix xml: . @prefix xsd: . -@prefix : . @prefix spw: . @prefix dcterms: . @prefix p-plan: . @prefix prov: . +@prefix : . @prefix terms: . @prefix vann: . @prefix dtype: . diff --git a/docs/mcp-demo/foaf-social-network/01-tbox.svg b/docs/mcp-demo/foaf-social-network/01-tbox.svg index 03b3b409..4503574a 100644 --- a/docs/mcp-demo/foaf-social-network/01-tbox.svg +++ b/docs/mcp-demo/foaf-social-network/01-tbox.svg @@ -1,6 +1,7 @@
owl:Class
A
Agent
IRI
http://xmlns.com/foaf/0.1/Agent
No annotation properties
owl:Class
P
Person
IRI
http://xmlns.com/foaf/0.1/Person
No annotation properties
owl:Class
O
Organization
IRI
http://xmlns.com/foaf/0.1/Organization
No annotation properties
owl:ObjectProperty
K
knows
IRI
http://xmlns.com/foaf/0.1/knows
No annotation properties
owl:ObjectProperty
M
member
IRI
http://xmlns.com/foaf/0.1/member
No annotation properties
owl:Class
E
Employee
IRI
http://example.org/ex#Employee
No annotation properties
owl:Class
M
Manager
IRI
http://example.org/ex#Manager
No annotation properties
owl:Class
E
Executive
IRI
http://example.org/ex#Executive
No annotation properties
owl:Class
D
Department
IRI
http://example.org/ex#Department
No annotation properties
owl:ObjectProperty
M
manages
IRI
http://example.org/ex#manages
No annotation properties
owl:ObjectProperty
R
reportsTo
IRI
http://example.org/ex#reportsTo
No annotation properties
owl:ObjectProperty, owl:SymmetricProperty
C
collaboratesWith
IRI
http://example.org/ex#collaboratesWith
No annotation properties
\ No newline at end of file +* { border-color: var(--border); }
owl:Class
A
Agent
IRI
http://xmlns.com/foaf/0.1/Agent
No annotation properties
owl:Class
P
Person
IRI
http://xmlns.com/foaf/0.1/Person
No annotation properties
owl:Class
O
Organization
IRI
http://xmlns.com/foaf/0.1/Organization
No annotation properties
owl:ObjectProperty
K
knows
IRI
http://xmlns.com/foaf/0.1/knows
No annotation properties
owl:ObjectProperty
M
member
IRI
http://xmlns.com/foaf/0.1/member
No annotation properties
owl:Class
E
Employee
IRI
http://example.org/ex#Employee
No annotation properties
owl:Class
M
Manager
IRI
http://example.org/ex#Manager
No annotation properties
owl:Class
E
Executive
IRI
http://example.org/ex#Executive
No annotation properties
owl:Class
D
Department
IRI
http://example.org/ex#Department
No annotation properties
owl:ObjectProperty
M
manages
IRI
http://example.org/ex#manages
No annotation properties
owl:ObjectProperty
R
reportsTo
IRI
http://example.org/ex#reportsTo
No annotation properties
owl:ObjectProperty, owl:SymmetricProperty
C
collaboratesWith
IRI
http://example.org/ex#collaboratesWith
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/foaf-social-network/02-before-reasoning.svg b/docs/mcp-demo/foaf-social-network/02-before-reasoning.svg index d0b2ebee..9d4702f3 100644 --- a/docs/mcp-demo/foaf-social-network/02-before-reasoning.svg +++ b/docs/mcp-demo/foaf-social-network/02-before-reasoning.svg @@ -1,6 +1,7 @@
Executive
A
Alice
IRI
http://example.org/alice
knows
http://example.org/eve
manages
http://example.org/bob
http://example.org/frank
B
Bob
IRI
http://example.org/bob
knows
http://example.org/carol
manages
http://example.org/carol
http://example.org/dave
C
Carol
IRI
http://example.org/carol
reportsTo
http://example.org/bob
D
Dave
IRI
http://example.org/dave
reportsTo
http://example.org/bob
collaboratesWith
http://example.org/eve
E
Eve
IRI
http://example.org/eve
No annotation properties
F
Frank
IRI
http://example.org/frank
No annotation properties
Department
E
Engineering
IRI
http://example.org/engineering
No annotation properties
Department
R
Research
IRI
http://example.org/research
No annotation properties
\ No newline at end of file +* { border-color: var(--border); }
Executive
A
Alice
IRI
http://example.org/alice
knows
http://example.org/eve
manages
http://example.org/bob
http://example.org/frank
B
Bob
IRI
http://example.org/bob
knows
http://example.org/carol
manages
http://example.org/carol
http://example.org/dave
C
Carol
IRI
http://example.org/carol
reportsTo
http://example.org/bob
D
Dave
IRI
http://example.org/dave
reportsTo
http://example.org/bob
collaboratesWith
http://example.org/eve
E
Eve
IRI
http://example.org/eve
No annotation properties
F
Frank
IRI
http://example.org/frank
No annotation properties
Department
E
Engineering
IRI
http://example.org/engineering
No annotation properties
Department
R
Research
IRI
http://example.org/research
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/foaf-social-network/03-after-reasoning.svg b/docs/mcp-demo/foaf-social-network/03-after-reasoning.svg index 7f4a555d..90797731 100644 --- a/docs/mcp-demo/foaf-social-network/03-after-reasoning.svg +++ b/docs/mcp-demo/foaf-social-network/03-after-reasoning.svg @@ -1,6 +1,7 @@
Employee, Executive, Manager, Agent, Person
A
Alice
IRI
http://example.org/alice
No annotation properties
Employee, Manager, Agent, Person
B
Bob
IRI
http://example.org/bob
No annotation properties
Employee, Agent, Person
C
Carol
IRI
http://example.org/carol
No annotation properties
Employee, Agent, Person
D
Dave
IRI
http://example.org/dave
No annotation properties
Employee, Agent, Person
E
Eve
IRI
http://example.org/eve
No annotation properties
Employee, Agent, Person
F
Frank
IRI
http://example.org/frank
No annotation properties
Department, Agent, Organization
E
Engineering
IRI
http://example.org/engineering
No annotation properties
Department, Agent, Organization
R
Research
IRI
http://example.org/research
No annotation properties
\ No newline at end of file +* { border-color: var(--border); }
Employee, Executive, Manager, Agent, Person
A
Alice
IRI
http://example.org/alice
No annotation properties
Employee, Manager, Agent, Person
B
Bob
IRI
http://example.org/bob
No annotation properties
Employee, Agent, Person
C
Carol
IRI
http://example.org/carol
No annotation properties
Employee, Agent, Person
D
Dave
IRI
http://example.org/dave
No annotation properties
Employee, Agent, Person
E
Eve
IRI
http://example.org/eve
No annotation properties
Employee, Agent, Person
F
Frank
IRI
http://example.org/frank
No annotation properties
Department, Agent, Organization
E
Engineering
IRI
http://example.org/engineering
No annotation properties
Department, Agent, Organization
R
Research
IRI
http://example.org/research
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/foaf-social-network/04-frank-focus.svg b/docs/mcp-demo/foaf-social-network/04-frank-focus.svg index 2621783f..64660c61 100644 --- a/docs/mcp-demo/foaf-social-network/04-frank-focus.svg +++ b/docs/mcp-demo/foaf-social-network/04-frank-focus.svg @@ -1,6 +1,7 @@
Employee, Executive, Manager, Agent, Person
A
Alice
IRI
http://example.org/alice
No annotation properties
Employee, Manager, Agent, Person
B
Bob
IRI
http://example.org/bob
No annotation properties
Employee, Agent, Person
C
Carol
IRI
http://example.org/carol
No annotation properties
Employee, Agent, Person
D
Dave
IRI
http://example.org/dave
No annotation properties
Employee, Agent, Person
E
Eve
IRI
http://example.org/eve
No annotation properties
Employee, Agent, Person
F
Frank
IRI
http://example.org/frank
No annotation properties
Department, Agent, Organization
E
Engineering
IRI
http://example.org/engineering
No annotation properties
Department, Agent, Organization
R
Research
IRI
http://example.org/research
No annotation properties
\ No newline at end of file +* { border-color: var(--border); }
Employee, Executive, Manager, Agent, Person
A
Alice
IRI
http://example.org/alice
No annotation properties
Employee, Manager, Agent, Person
B
Bob
IRI
http://example.org/bob
No annotation properties
Employee, Agent, Person
C
Carol
IRI
http://example.org/carol
No annotation properties
Employee, Agent, Person
D
Dave
IRI
http://example.org/dave
No annotation properties
Employee, Agent, Person
E
Eve
IRI
http://example.org/eve
No annotation properties
Employee, Agent, Person
F
Frank
IRI
http://example.org/frank
No annotation properties
Department, Agent, Organization
E
Engineering
IRI
http://example.org/engineering
No annotation properties
Department, Agent, Organization
R
Research
IRI
http://example.org/research
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/foaf-social-network/graph.ttl b/docs/mcp-demo/foaf-social-network/graph.ttl index c6d32f3b..b60dfdd8 100644 --- a/docs/mcp-demo/foaf-social-network/graph.ttl +++ b/docs/mcp-demo/foaf-social-network/graph.ttl @@ -5,11 +5,11 @@ @prefix rdfs: . @prefix xml: . @prefix xsd: . -@prefix : . @prefix spw: . @prefix dcterms: . @prefix p-plan: . @prefix prov: . +@prefix : . @prefix terms: . @prefix vann: . @prefix dtype: . diff --git a/docs/mcp-demo/pizza-tutorial.md b/docs/mcp-demo/pizza-tutorial.md index 0167b9b3..290cc624 100644 --- a/docs/mcp-demo/pizza-tutorial.md +++ b/docs/mcp-demo/pizza-tutorial.md @@ -53,16 +53,16 @@ pairs to cover every combination.
4 tool calls ✓ -`{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` -`{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` -`{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` +`{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` +`{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` +`{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` `{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":200}}}` ```tool-result [Ontosphere — 4 tools ✓] -✓ addLink: s=Pizza p=disjointWith o=PizzaBase -✓ addLink: s=Pizza p=disjointWith o=PizzaTopping -✓ addLink: s=PizzaBase p=disjointWith o=PizzaTopping +✓ addTriple +✓ addTriple +✓ addTriple ✓ runLayout: elk-layered Canvas: 3 nodes, 3 links @@ -86,18 +86,18 @@ also disjoint: no base can be both thin and deep pan. `{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"ThinAndCrispyBase"}}}` `{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#DeepPanBase","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"DeepPanBase"}}}` -`{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` -`{"jsonrpc":"2.0","id":13,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#DeepPanBase","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` -`{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#DeepPanBase"}}}` +`{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` +`{"jsonrpc":"2.0","id":13,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#DeepPanBase","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` +`{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#DeepPanBase"}}}` `{"jsonrpc":"2.0","id":15,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":160}}}` ```tool-result [Ontosphere — 6 tools ✓] ✓ addNode: http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase ✓ addNode: http://www.pizza-ontology.com/pizza.owl#DeepPanBase -✓ addLink: s=ThinAndCrispyBase p=subClassOf o=PizzaBase -✓ addLink: s=DeepPanBase p=subClassOf o=PizzaBase -✓ addLink: s=ThinAndCrispyBase p=disjointWith o=DeepPanBase +✓ addTriple +✓ addTriple +✓ addTriple ✓ runLayout: elk-layered Canvas: 5 nodes, 6 links @@ -123,10 +123,10 @@ are also implicitly Pizzas through the chain. `{"jsonrpc":"2.0","id":17,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#Margherita","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"Margherita"}}}` `{"jsonrpc":"2.0","id":18,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#AmericanHot","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"AmericanHot"}}}` `{"jsonrpc":"2.0","id":19,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#FruttiDiMare","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"FruttiDiMare"}}}` -`{"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` -`{"jsonrpc":"2.0","id":21,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#Margherita","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza"}}}` -`{"jsonrpc":"2.0","id":22,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#AmericanHot","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza"}}}` -`{"jsonrpc":"2.0","id":23,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#FruttiDiMare","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza"}}}` +`{"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` +`{"jsonrpc":"2.0","id":21,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#Margherita","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza"}}}` +`{"jsonrpc":"2.0","id":22,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#AmericanHot","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza"}}}` +`{"jsonrpc":"2.0","id":23,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#FruttiDiMare","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza"}}}` `{"jsonrpc":"2.0","id":24,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":160}}}` ```tool-result @@ -135,10 +135,10 @@ are also implicitly Pizzas through the chain. ✓ addNode: http://www.pizza-ontology.com/pizza.owl#Margherita ✓ addNode: http://www.pizza-ontology.com/pizza.owl#AmericanHot ✓ addNode: http://www.pizza-ontology.com/pizza.owl#FruttiDiMare -✓ addLink: s=NamedPizza p=subClassOf o=Pizza -✓ addLink: s=Margherita p=subClassOf o=NamedPizza -✓ addLink: s=AmericanHot p=subClassOf o=NamedPizza -✓ addLink: s=FruttiDiMare p=subClassOf o=NamedPizza +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple ✓ runLayout: elk-layered Canvas: 9 nodes, 10 links @@ -164,10 +164,10 @@ PizzaTopping through the chain. `{"jsonrpc":"2.0","id":26,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"MeatTopping"}}}` `{"jsonrpc":"2.0","id":27,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"VegetableTopping"}}}` `{"jsonrpc":"2.0","id":28,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#FishTopping","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"FishTopping"}}}` -`{"jsonrpc":"2.0","id":29,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` -`{"jsonrpc":"2.0","id":30,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` -`{"jsonrpc":"2.0","id":31,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` -`{"jsonrpc":"2.0","id":32,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` +`{"jsonrpc":"2.0","id":29,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` +`{"jsonrpc":"2.0","id":30,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` +`{"jsonrpc":"2.0","id":31,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` +`{"jsonrpc":"2.0","id":32,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` `{"jsonrpc":"2.0","id":33,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":160}}}` ```tool-result @@ -176,10 +176,10 @@ PizzaTopping through the chain. ✓ addNode: http://www.pizza-ontology.com/pizza.owl#MeatTopping ✓ addNode: http://www.pizza-ontology.com/pizza.owl#VegetableTopping ✓ addNode: http://www.pizza-ontology.com/pizza.owl#FishTopping -✓ addLink: s=CheeseTopping p=subClassOf o=PizzaTopping -✓ addLink: s=MeatTopping p=subClassOf o=PizzaTopping -✓ addLink: s=VegetableTopping p=subClassOf o=PizzaTopping -✓ addLink: s=FishTopping p=subClassOf o=PizzaTopping +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple ✓ runLayout: elk-layered Canvas: 13 nodes, 14 links @@ -202,22 +202,22 @@ it will flag an inconsistency.
7 tool calls ✓ -`{"jsonrpc":"2.0","id":34,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping"}}}` -`{"jsonrpc":"2.0","id":35,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` -`{"jsonrpc":"2.0","id":36,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping"}}}` -`{"jsonrpc":"2.0","id":37,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` -`{"jsonrpc":"2.0","id":38,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping"}}}` -`{"jsonrpc":"2.0","id":39,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping"}}}` +`{"jsonrpc":"2.0","id":34,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping"}}}` +`{"jsonrpc":"2.0","id":35,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` +`{"jsonrpc":"2.0","id":36,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping"}}}` +`{"jsonrpc":"2.0","id":37,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` +`{"jsonrpc":"2.0","id":38,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping"}}}` +`{"jsonrpc":"2.0","id":39,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping"}}}` `{"jsonrpc":"2.0","id":40,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":160}}}` ```tool-result [Ontosphere — 7 tools ✓] -✓ addLink: s=CheeseTopping p=disjointWith o=MeatTopping -✓ addLink: s=CheeseTopping p=disjointWith o=VegetableTopping -✓ addLink: s=CheeseTopping p=disjointWith o=FishTopping -✓ addLink: s=MeatTopping p=disjointWith o=VegetableTopping -✓ addLink: s=MeatTopping p=disjointWith o=FishTopping -✓ addLink: s=VegetableTopping p=disjointWith o=FishTopping +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple ✓ runLayout: elk-layered Canvas: 13 nodes, 20 links @@ -246,13 +246,13 @@ all the way up to CheeseTopping and then PizzaTopping — no extra assertions ne `{"jsonrpc":"2.0","id":45,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#OliveTopping","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"OliveTopping"}}}` `{"jsonrpc":"2.0","id":46,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#GarlicTopping","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"GarlicTopping"}}}` `{"jsonrpc":"2.0","id":47,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#AnchoviesTopping","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"AnchoviesTopping"}}}` -`{"jsonrpc":"2.0","id":48,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#MozzarellaTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping"}}}` -`{"jsonrpc":"2.0","id":49,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#ParmesanTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping"}}}` -`{"jsonrpc":"2.0","id":50,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#PeperoniSausageTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping"}}}` -`{"jsonrpc":"2.0","id":51,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#TomatoTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` -`{"jsonrpc":"2.0","id":52,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#OliveTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` -`{"jsonrpc":"2.0","id":53,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#GarlicTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` -`{"jsonrpc":"2.0","id":54,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#AnchoviesTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping"}}}` +`{"jsonrpc":"2.0","id":48,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#MozzarellaTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping"}}}` +`{"jsonrpc":"2.0","id":49,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#ParmesanTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping"}}}` +`{"jsonrpc":"2.0","id":50,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#PeperoniSausageTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping"}}}` +`{"jsonrpc":"2.0","id":51,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#TomatoTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` +`{"jsonrpc":"2.0","id":52,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#OliveTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` +`{"jsonrpc":"2.0","id":53,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#GarlicTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` +`{"jsonrpc":"2.0","id":54,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#AnchoviesTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping"}}}` `{"jsonrpc":"2.0","id":55,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":140}}}` `{"jsonrpc":"2.0","id":56,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":57,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":140}}}` @@ -266,13 +266,13 @@ all the way up to CheeseTopping and then PizzaTopping — no extra assertions ne ✓ addNode: http://www.pizza-ontology.com/pizza.owl#OliveTopping ✓ addNode: http://www.pizza-ontology.com/pizza.owl#GarlicTopping ✓ addNode: http://www.pizza-ontology.com/pizza.owl#AnchoviesTopping -✓ addLink: s=MozzarellaTopping p=subClassOf o=CheeseTopping -✓ addLink: s=ParmesanTopping p=subClassOf o=CheeseTopping -✓ addLink: s=PeperoniSausageTopping p=subClassOf o=MeatTopping -✓ addLink: s=TomatoTopping p=subClassOf o=VegetableTopping -✓ addLink: s=OliveTopping p=subClassOf o=VegetableTopping -✓ addLink: s=GarlicTopping p=subClassOf o=VegetableTopping -✓ addLink: s=AnchoviesTopping p=subClassOf o=FishTopping +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple ✓ runLayout: elk-layered ✓ expandNode ✓ runLayout: elk-layered @@ -299,20 +299,20 @@ explicit type assertion. `{"jsonrpc":"2.0","id":58,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"hasTopping"}}}` `{"jsonrpc":"2.0","id":59,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#hasBase","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"hasBase"}}}` -`{"jsonrpc":"2.0","id":60,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` -`{"jsonrpc":"2.0","id":61,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` -`{"jsonrpc":"2.0","id":62,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` -`{"jsonrpc":"2.0","id":63,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` +`{"jsonrpc":"2.0","id":60,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` +`{"jsonrpc":"2.0","id":61,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` +`{"jsonrpc":"2.0","id":62,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` +`{"jsonrpc":"2.0","id":63,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` `{"jsonrpc":"2.0","id":64,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":140}}}` ```tool-result [Ontosphere — 7 tools ✓] ✓ addNode: http://www.pizza-ontology.com/pizza.owl#hasTopping ✓ addNode: http://www.pizza-ontology.com/pizza.owl#hasBase -✓ addLink: s=hasTopping p=domain o=Pizza -✓ addLink: s=hasTopping p=range o=PizzaTopping -✓ addLink: s=hasBase p=domain o=Pizza -✓ addLink: s=hasBase p=range o=PizzaBase +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple ✓ runLayout: elk-layered Canvas: 22 nodes, 31 links @@ -337,13 +337,13 @@ on the real world. `{"jsonrpc":"2.0","id":65,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#isToppingOf","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"isToppingOf"}}}` `{"jsonrpc":"2.0","id":66,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#isBaseOf","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"isBaseOf"}}}` -`{"jsonrpc":"2.0","id":67,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isToppingOf","predicateIri":"http://www.w3.org/2002/07/owl#inverseOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping"}}}` -`{"jsonrpc":"2.0","id":68,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isBaseOf","predicateIri":"http://www.w3.org/2002/07/owl#inverseOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#hasBase"}}}` -`{"jsonrpc":"2.0","id":69,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isToppingOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` -`{"jsonrpc":"2.0","id":70,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isToppingOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` -`{"jsonrpc":"2.0","id":71,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isBaseOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` -`{"jsonrpc":"2.0","id":72,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isBaseOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` -`{"jsonrpc":"2.0","id":73,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","predicateIri":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","objectIri":"http://www.w3.org/2002/07/owl#FunctionalProperty"}}}` +`{"jsonrpc":"2.0","id":67,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isToppingOf","predicateIri":"http://www.w3.org/2002/07/owl#inverseOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping"}}}` +`{"jsonrpc":"2.0","id":68,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isBaseOf","predicateIri":"http://www.w3.org/2002/07/owl#inverseOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#hasBase"}}}` +`{"jsonrpc":"2.0","id":69,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isToppingOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` +`{"jsonrpc":"2.0","id":70,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isToppingOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` +`{"jsonrpc":"2.0","id":71,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isBaseOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` +`{"jsonrpc":"2.0","id":72,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isBaseOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` +`{"jsonrpc":"2.0","id":73,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","predicateIri":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","objectIri":"http://www.w3.org/2002/07/owl#FunctionalProperty"}}}` `{"jsonrpc":"2.0","id":74,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":140}}}` `{"jsonrpc":"2.0","id":75,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":76,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":140}}}` @@ -352,13 +352,13 @@ on the real world. [Ontosphere — 12 tools ✓] ✓ addNode: http://www.pizza-ontology.com/pizza.owl#isToppingOf ✓ addNode: http://www.pizza-ontology.com/pizza.owl#isBaseOf -✓ addLink: s=isToppingOf p=inverseOf o=hasTopping -✓ addLink: s=isBaseOf p=inverseOf o=hasBase -✓ addLink: s=isToppingOf p=domain o=PizzaTopping -✓ addLink: s=isToppingOf p=range o=Pizza -✓ addLink: s=isBaseOf p=domain o=PizzaBase -✓ addLink: s=isBaseOf p=range o=Pizza -✓ addLink: s=hasBase p=type o=FunctionalProperty +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple ✓ runLayout: elk-layered ✓ expandNode ✓ runLayout: elk-layered @@ -441,14 +441,14 @@ closure axioms exist. 4 tool calls ✓ `{"jsonrpc":"2.0","id":128,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#VegetarianPizza","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"VegetarianPizza"}}}` -`{"jsonrpc":"2.0","id":129,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#VegetarianPizza","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza"}}}` +`{"jsonrpc":"2.0","id":129,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#VegetarianPizza","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza"}}}` `{"jsonrpc":"2.0","id":130,"method":"tools/call","params":{"name":"loadRdf","arguments":{"turtle":"@prefix owl: .\n@prefix rdf: .\n@prefix pizza: .\n\npizza:VegetarianPizza owl:equivalentClass [\n rdf:type owl:Restriction ;\n owl:onProperty pizza:hasTopping ;\n owl:someValuesFrom pizza:VegetableTopping\n] ."}}}` `{"jsonrpc":"2.0","id":131,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":140}}}` ```tool-result [Ontosphere — 4 tools ✓] ✓ addNode: http://www.pizza-ontology.com/pizza.owl#VegetarianPizza -✓ addLink: s=VegetarianPizza p=subClassOf o=NamedPizza +✓ addTriple ✓ loadRdf: loaded ✓ runLayout: elk-layered @@ -508,9 +508,9 @@ the most specific leaf class; the subClassOf chain propagates membership upward `{"jsonrpc":"2.0","id":82,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#mozz1","typeIri":"http://www.pizza-ontology.com/pizza.owl#MozzarellaTopping","label":"mozz"}}}` `{"jsonrpc":"2.0","id":83,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#tom1","typeIri":"http://www.pizza-ontology.com/pizza.owl#TomatoTopping","label":"tomato"}}}` `{"jsonrpc":"2.0","id":84,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#thin1","typeIri":"http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase","label":"thin & crispy"}}}` -`{"jsonrpc":"2.0","id":85,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza1","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#mozz1"}}}` -`{"jsonrpc":"2.0","id":86,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza1","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#tom1"}}}` -`{"jsonrpc":"2.0","id":87,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza1","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","objectIri":"http://www.pizza-ontology.com/pizza.owl#thin1"}}}` +`{"jsonrpc":"2.0","id":85,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza1","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#mozz1"}}}` +`{"jsonrpc":"2.0","id":86,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza1","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#tom1"}}}` +`{"jsonrpc":"2.0","id":87,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza1","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","objectIri":"http://www.pizza-ontology.com/pizza.owl#thin1"}}}` `{"jsonrpc":"2.0","id":88,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":180}}}` `{"jsonrpc":"2.0","id":200,"method":"tools/call","params":{"name":"focusNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#pizza1"}}}` @@ -519,9 +519,9 @@ the most specific leaf class; the subClassOf chain propagates membership upward ✓ addNode: http://www.pizza-ontology.com/pizza.owl#mozz1 ✓ addNode: http://www.pizza-ontology.com/pizza.owl#tom1 ✓ addNode: http://www.pizza-ontology.com/pizza.owl#thin1 -✓ addLink: s=pizza1 p=hasTopping o=mozz1 -✓ addLink: s=pizza1 p=hasTopping o=tom1 -✓ addLink: s=pizza1 p=hasBase o=thin1 +✓ addTriple +✓ addTriple +✓ addTriple ✓ runLayout: dagre-lr ✓ focusNode @@ -548,10 +548,10 @@ so no conflict. `{"jsonrpc":"2.0","id":92,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#mozz2","typeIri":"http://www.pizza-ontology.com/pizza.owl#MozzarellaTopping","label":"mozz"}}}` `{"jsonrpc":"2.0","id":93,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#olive1","typeIri":"http://www.pizza-ontology.com/pizza.owl#OliveTopping","label":"olive"}}}` `{"jsonrpc":"2.0","id":94,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#deep1","typeIri":"http://www.pizza-ontology.com/pizza.owl#DeepPanBase","label":"deep pan"}}}` -`{"jsonrpc":"2.0","id":95,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza2","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#pep1"}}}` -`{"jsonrpc":"2.0","id":96,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza2","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#mozz2"}}}` -`{"jsonrpc":"2.0","id":97,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza2","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#olive1"}}}` -`{"jsonrpc":"2.0","id":98,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza2","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","objectIri":"http://www.pizza-ontology.com/pizza.owl#deep1"}}}` +`{"jsonrpc":"2.0","id":95,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza2","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#pep1"}}}` +`{"jsonrpc":"2.0","id":96,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza2","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#mozz2"}}}` +`{"jsonrpc":"2.0","id":97,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza2","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#olive1"}}}` +`{"jsonrpc":"2.0","id":98,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza2","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","objectIri":"http://www.pizza-ontology.com/pizza.owl#deep1"}}}` `{"jsonrpc":"2.0","id":99,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":180}}}` `{"jsonrpc":"2.0","id":201,"method":"tools/call","params":{"name":"focusNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#pizza2"}}}` @@ -561,10 +561,10 @@ so no conflict. ✓ addNode: http://www.pizza-ontology.com/pizza.owl#mozz2 ✓ addNode: http://www.pizza-ontology.com/pizza.owl#olive1 ✓ addNode: http://www.pizza-ontology.com/pizza.owl#deep1 -✓ addLink: s=pizza2 p=hasTopping o=pep1 -✓ addLink: s=pizza2 p=hasTopping o=mozz2 -✓ addLink: s=pizza2 p=hasTopping o=olive1 -✓ addLink: s=pizza2 p=hasBase o=deep1 +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple ✓ runLayout: dagre-lr ✓ focusNode @@ -591,9 +591,9 @@ topping the reasoner will use for classification. `{"jsonrpc":"2.0","id":100,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#anch1","typeIri":"http://www.pizza-ontology.com/pizza.owl#AnchoviesTopping","label":"anchovies"}}}` `{"jsonrpc":"2.0","id":101,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#garlic1","typeIri":"http://www.pizza-ontology.com/pizza.owl#GarlicTopping","label":"garlic"}}}` `{"jsonrpc":"2.0","id":102,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#thin2","typeIri":"http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase","label":"thin & crispy"}}}` -`{"jsonrpc":"2.0","id":103,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza3","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#anch1"}}}` -`{"jsonrpc":"2.0","id":104,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza3","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#garlic1"}}}` -`{"jsonrpc":"2.0","id":105,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza3","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","objectIri":"http://www.pizza-ontology.com/pizza.owl#thin2"}}}` +`{"jsonrpc":"2.0","id":103,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza3","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#anch1"}}}` +`{"jsonrpc":"2.0","id":104,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza3","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#garlic1"}}}` +`{"jsonrpc":"2.0","id":105,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza3","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","objectIri":"http://www.pizza-ontology.com/pizza.owl#thin2"}}}` `{"jsonrpc":"2.0","id":106,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":107,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":180}}}` @@ -602,9 +602,9 @@ topping the reasoner will use for classification. ✓ addNode: http://www.pizza-ontology.com/pizza.owl#anch1 ✓ addNode: http://www.pizza-ontology.com/pizza.owl#garlic1 ✓ addNode: http://www.pizza-ontology.com/pizza.owl#thin2 -✓ addLink: s=pizza3 p=hasTopping o=anch1 -✓ addLink: s=pizza3 p=hasTopping o=garlic1 -✓ addLink: s=pizza3 p=hasBase o=thin2 +✓ addTriple +✓ addTriple +✓ addTriple ✓ expandNode ✓ runLayout: dagre-lr @@ -632,9 +632,9 @@ fire and classify it — but we also need to observe what the reasoner says abou `{"jsonrpc":"2.0","id":136,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#olive2","typeIri":"http://www.pizza-ontology.com/pizza.owl#OliveTopping","label":"olive"}}}` `{"jsonrpc":"2.0","id":137,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#tom2","typeIri":"http://www.pizza-ontology.com/pizza.owl#TomatoTopping","label":"tomato"}}}` `{"jsonrpc":"2.0","id":138,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#thin3","typeIri":"http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase","label":"thin & crispy"}}}` -`{"jsonrpc":"2.0","id":139,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza4","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#olive2"}}}` -`{"jsonrpc":"2.0","id":140,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza4","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#tom2"}}}` -`{"jsonrpc":"2.0","id":141,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza4","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","objectIri":"http://www.pizza-ontology.com/pizza.owl#thin3"}}}` +`{"jsonrpc":"2.0","id":139,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza4","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#olive2"}}}` +`{"jsonrpc":"2.0","id":140,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza4","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#tom2"}}}` +`{"jsonrpc":"2.0","id":141,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza4","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","objectIri":"http://www.pizza-ontology.com/pizza.owl#thin3"}}}` `{"jsonrpc":"2.0","id":142,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":143,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":180}}}` `{"jsonrpc":"2.0","id":202,"method":"tools/call","params":{"name":"focusNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#pizza4"}}}` @@ -645,9 +645,9 @@ fire and classify it — but we also need to observe what the reasoner says abou ✓ addNode: http://www.pizza-ontology.com/pizza.owl#olive2 ✓ addNode: http://www.pizza-ontology.com/pizza.owl#tom2 ✓ addNode: http://www.pizza-ontology.com/pizza.owl#thin3 -✓ addLink: s=pizza4 p=hasTopping o=olive2 -✓ addLink: s=pizza4 p=hasTopping o=tom2 -✓ addLink: s=pizza4 p=hasBase o=thin3 +✓ addTriple +✓ addTriple +✓ addTriple ✓ expandNode ✓ runLayout: dagre-lr ✓ focusNode @@ -689,7 +689,7 @@ fires on our data: ✓ expandNode ✓ runLayout: dagre-lr -Canvas: 21 nodes, 26 links +Canvas: 21 nodes, 43 links ```
@@ -719,7 +719,7 @@ then cax-sco propagates). Not one of these types was explicitly stated in the AB ✓ focusNode ✓ expandNode -Canvas: 21 nodes, 26 links +Canvas: 21 nodes, 43 links ```
@@ -746,7 +746,7 @@ Each step was derived automatically from the subClassOf chain in the TBox. ✓ focusNode ✓ expandNode -Canvas: 21 nodes, 26 links +Canvas: 21 nodes, 43 links ```
@@ -786,7 +786,7 @@ reasoner or a SHACL constraint. ✓ focusNode ✓ expandNode -Canvas: 21 nodes, 26 links +Canvas: 21 nodes, 43 links ``` diff --git a/docs/mcp-demo/pizza-tutorial/01-root-classes-bare.svg b/docs/mcp-demo/pizza-tutorial/01-root-classes-bare.svg index 539d2106..c4e24756 100644 --- a/docs/mcp-demo/pizza-tutorial/01-root-classes-bare.svg +++ b/docs/mcp-demo/pizza-tutorial/01-root-classes-bare.svg @@ -1,6 +1,7 @@
owl:Class
P
Pizza
owl:Class
P
PizzaBase
owl:Class
P
PizzaTopping
\ No newline at end of file +* { border-color: var(--border); }
owl:Class
P
Pizza
owl:Class
P
PizzaBase
owl:Class
P
PizzaTopping
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/02-root-disjoint.svg b/docs/mcp-demo/pizza-tutorial/02-root-disjoint.svg index 8dfdc399..edafbaf1 100644 --- a/docs/mcp-demo/pizza-tutorial/02-root-disjoint.svg +++ b/docs/mcp-demo/pizza-tutorial/02-root-disjoint.svg @@ -1,6 +1,7 @@
owl:Class
P
Pizza
owl:Class
P
PizzaBase
owl:Class
P
PizzaTopping
\ No newline at end of file +* { border-color: var(--border); }
owl:Class
P
Pizza
owl:Class
P
PizzaBase
owl:Class
P
PizzaTopping
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/03-base-hierarchy.svg b/docs/mcp-demo/pizza-tutorial/03-base-hierarchy.svg index 04d217ab..6b2cc114 100644 --- a/docs/mcp-demo/pizza-tutorial/03-base-hierarchy.svg +++ b/docs/mcp-demo/pizza-tutorial/03-base-hierarchy.svg @@ -1,6 +1,7 @@
owl:Class
P
Pizza
owl:Class
P
PizzaBase
owl:Class
P
PizzaTopping
owl:Class
T
ThinAndCrispyBase
owl:Class
D
DeepPanBase
\ No newline at end of file +* { border-color: var(--border); }
owl:Class
P
Pizza
owl:Class
P
PizzaBase
owl:Class
P
PizzaTopping
owl:Class
T
ThinAndCrispyBase
owl:Class
D
DeepPanBase
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/04-named-pizza-hierarchy.svg b/docs/mcp-demo/pizza-tutorial/04-named-pizza-hierarchy.svg index a3c75623..033404a0 100644 --- a/docs/mcp-demo/pizza-tutorial/04-named-pizza-hierarchy.svg +++ b/docs/mcp-demo/pizza-tutorial/04-named-pizza-hierarchy.svg @@ -1,6 +1,7 @@
owl:Class
P
Pizza
owl:Class
P
PizzaBase
owl:Class
P
PizzaTopping
owl:Class
T
ThinAndCrispyBase
owl:Class
D
DeepPanBase
owl:Class
N
NamedPizza
owl:Class
M
Margherita
owl:Class
A
AmericanHot
owl:Class
F
FruttiDiMare
\ No newline at end of file +* { border-color: var(--border); }
owl:Class
P
Pizza
owl:Class
P
PizzaBase
owl:Class
P
PizzaTopping
owl:Class
T
ThinAndCrispyBase
owl:Class
D
DeepPanBase
owl:Class
N
NamedPizza
owl:Class
M
Margherita
owl:Class
A
AmericanHot
owl:Class
F
FruttiDiMare
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/05-topping-categories.svg b/docs/mcp-demo/pizza-tutorial/05-topping-categories.svg index 01490397..d5d1046e 100644 --- a/docs/mcp-demo/pizza-tutorial/05-topping-categories.svg +++ b/docs/mcp-demo/pizza-tutorial/05-topping-categories.svg @@ -1,6 +1,7 @@ -
owl:Class
P
Pizza
owl:Class
P
PizzaBase
owl:Class
P
PizzaTopping
owl:Class
T
ThinAndCrispyBase
owl:Class
D
DeepPanBase
owl:Class
N
NamedPizza
owl:Class
M
Margherita
owl:Class
A
AmericanHot
owl:Class
F
FruttiDiMare
owl:Class
C
CheeseTopping
owl:Class
M
MeatTopping
owl:Class
V
VegetableTopping
owl:Class
F
FishTopping
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/06-topping-disjoint.svg b/docs/mcp-demo/pizza-tutorial/06-topping-disjoint.svg index 58c3a8bc..b5dfbacb 100644 --- a/docs/mcp-demo/pizza-tutorial/06-topping-disjoint.svg +++ b/docs/mcp-demo/pizza-tutorial/06-topping-disjoint.svg @@ -1,6 +1,7 @@ -
owl:Class
P
Pizza
owl:Class
P
PizzaBase
owl:Class
P
PizzaTopping
owl:Class
T
ThinAndCrispyBase
owl:Class
D
DeepPanBase
owl:Class
N
NamedPizza
owl:Class
M
Margherita
owl:Class
A
AmericanHot
owl:Class
F
FruttiDiMare
owl:Class
C
CheeseTopping
owl:Class
M
MeatTopping
owl:Class
V
VegetableTopping
owl:Class
F
FishTopping
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/07-tbox-leaf-toppings.svg b/docs/mcp-demo/pizza-tutorial/07-tbox-leaf-toppings.svg index 9c47bd84..e26a73e1 100644 --- a/docs/mcp-demo/pizza-tutorial/07-tbox-leaf-toppings.svg +++ b/docs/mcp-demo/pizza-tutorial/07-tbox-leaf-toppings.svg @@ -1,6 +1,7 @@ -
owl:Class
P
Pizza
IRI
http://www.pizza-ontology.com/pizza.owl#Pizza
No annotation properties
owl:Class
P
PizzaBase
IRI
http://www.pizza-ontology.com/pizza.owl#PizzaBase
No annotation properties
owl:Class
P
PizzaTopping
IRI
http://www.pizza-ontology.com/pizza.owl#PizzaTopping
No annotation properties
owl:Class
T
ThinAndCrispyBase
IRI
http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase
No annotation properties
owl:Class
D
DeepPanBase
IRI
http://www.pizza-ontology.com/pizza.owl#DeepPanBase
No annotation properties
owl:Class
N
NamedPizza
IRI
http://www.pizza-ontology.com/pizza.owl#NamedPizza
No annotation properties
owl:Class
M
Margherita
IRI
http://www.pizza-ontology.com/pizza.owl#Margherita
No annotation properties
owl:Class
A
AmericanHot
IRI
http://www.pizza-ontology.com/pizza.owl#AmericanHot
No annotation properties
owl:Class
F
FruttiDiMare
IRI
http://www.pizza-ontology.com/pizza.owl#FruttiDiMare
No annotation properties
owl:Class
C
CheeseTopping
IRI
http://www.pizza-ontology.com/pizza.owl#CheeseTopping
No annotation properties
owl:Class
M
MeatTopping
IRI
http://www.pizza-ontology.com/pizza.owl#MeatTopping
No annotation properties
owl:Class
V
VegetableTopping
IRI
http://www.pizza-ontology.com/pizza.owl#VegetableTopping
No annotation properties
owl:Class
F
FishTopping
IRI
http://www.pizza-ontology.com/pizza.owl#FishTopping
No annotation properties
owl:Class
M
MozzarellaTopping
IRI
http://www.pizza-ontology.com/pizza.owl#MozzarellaTopping
No annotation properties
owl:Class
P
ParmesanTopping
IRI
http://www.pizza-ontology.com/pizza.owl#ParmesanTopping
No annotation properties
owl:Class
P
PeperoniSausageTopping
IRI
http://www.pizza-ontology.com/pizza.owl#PeperoniSausageTopping
No annotation properties
owl:Class
T
TomatoTopping
IRI
http://www.pizza-ontology.com/pizza.owl#TomatoTopping
No annotation properties
owl:Class
O
OliveTopping
IRI
http://www.pizza-ontology.com/pizza.owl#OliveTopping
No annotation properties
owl:Class
G
GarlicTopping
IRI
http://www.pizza-ontology.com/pizza.owl#GarlicTopping
No annotation properties
owl:Class
A
AnchoviesTopping
IRI
http://www.pizza-ontology.com/pizza.owl#AnchoviesTopping
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/08-properties-domain-range.svg b/docs/mcp-demo/pizza-tutorial/08-properties-domain-range.svg index 41ae2f7a..1767800e 100644 --- a/docs/mcp-demo/pizza-tutorial/08-properties-domain-range.svg +++ b/docs/mcp-demo/pizza-tutorial/08-properties-domain-range.svg @@ -1,6 +1,7 @@ -
owl:Class
P
Pizza
IRI
http://www.pizza-ontology.com/pizza.owl#Pizza
No annotation properties
owl:Class
P
PizzaBase
IRI
http://www.pizza-ontology.com/pizza.owl#PizzaBase
No annotation properties
owl:Class
P
PizzaTopping
IRI
http://www.pizza-ontology.com/pizza.owl#PizzaTopping
No annotation properties
owl:Class
T
ThinAndCrispyBase
IRI
http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase
No annotation properties
owl:Class
D
DeepPanBase
IRI
http://www.pizza-ontology.com/pizza.owl#DeepPanBase
No annotation properties
owl:Class
N
NamedPizza
IRI
http://www.pizza-ontology.com/pizza.owl#NamedPizza
No annotation properties
owl:Class
M
Margherita
IRI
http://www.pizza-ontology.com/pizza.owl#Margherita
No annotation properties
owl:Class
A
AmericanHot
IRI
http://www.pizza-ontology.com/pizza.owl#AmericanHot
No annotation properties
owl:Class
F
FruttiDiMare
IRI
http://www.pizza-ontology.com/pizza.owl#FruttiDiMare
No annotation properties
owl:Class
C
CheeseTopping
IRI
http://www.pizza-ontology.com/pizza.owl#CheeseTopping
No annotation properties
owl:Class
M
MeatTopping
IRI
http://www.pizza-ontology.com/pizza.owl#MeatTopping
No annotation properties
owl:Class
V
VegetableTopping
IRI
http://www.pizza-ontology.com/pizza.owl#VegetableTopping
No annotation properties
owl:Class
F
FishTopping
IRI
http://www.pizza-ontology.com/pizza.owl#FishTopping
No annotation properties
owl:Class
M
MozzarellaTopping
IRI
http://www.pizza-ontology.com/pizza.owl#MozzarellaTopping
No annotation properties
owl:Class
P
ParmesanTopping
IRI
http://www.pizza-ontology.com/pizza.owl#ParmesanTopping
No annotation properties
owl:Class
P
PeperoniSausageTopping
IRI
http://www.pizza-ontology.com/pizza.owl#PeperoniSausageTopping
No annotation properties
owl:Class
T
TomatoTopping
IRI
http://www.pizza-ontology.com/pizza.owl#TomatoTopping
No annotation properties
owl:Class
O
OliveTopping
IRI
http://www.pizza-ontology.com/pizza.owl#OliveTopping
No annotation properties
owl:Class
G
GarlicTopping
IRI
http://www.pizza-ontology.com/pizza.owl#GarlicTopping
No annotation properties
owl:Class
A
AnchoviesTopping
IRI
http://www.pizza-ontology.com/pizza.owl#AnchoviesTopping
No annotation properties
owl:ObjectProperty
H
hasTopping
owl:ObjectProperty
H
hasBase
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/09-tbox-complete.svg b/docs/mcp-demo/pizza-tutorial/09-tbox-complete.svg index 36a5015c..79a4b7cf 100644 --- a/docs/mcp-demo/pizza-tutorial/09-tbox-complete.svg +++ b/docs/mcp-demo/pizza-tutorial/09-tbox-complete.svg @@ -1,6 +1,7 @@ -
owl:Class
P
Pizza
IRI
http://www.pizza-ontology.com/pizza.owl#Pizza
No annotation properties
owl:Class
P
PizzaBase
IRI
http://www.pizza-ontology.com/pizza.owl#PizzaBase
No annotation properties
owl:Class
P
PizzaTopping
IRI
http://www.pizza-ontology.com/pizza.owl#PizzaTopping
No annotation properties
owl:Class
T
ThinAndCrispyBase
IRI
http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase
No annotation properties
owl:Class
D
DeepPanBase
IRI
http://www.pizza-ontology.com/pizza.owl#DeepPanBase
No annotation properties
owl:Class
N
NamedPizza
IRI
http://www.pizza-ontology.com/pizza.owl#NamedPizza
No annotation properties
owl:Class
M
Margherita
IRI
http://www.pizza-ontology.com/pizza.owl#Margherita
No annotation properties
owl:Class
A
AmericanHot
IRI
http://www.pizza-ontology.com/pizza.owl#AmericanHot
No annotation properties
owl:Class
F
FruttiDiMare
IRI
http://www.pizza-ontology.com/pizza.owl#FruttiDiMare
No annotation properties
owl:Class
C
CheeseTopping
IRI
http://www.pizza-ontology.com/pizza.owl#CheeseTopping
No annotation properties
owl:Class
M
MeatTopping
IRI
http://www.pizza-ontology.com/pizza.owl#MeatTopping
No annotation properties
owl:Class
V
VegetableTopping
IRI
http://www.pizza-ontology.com/pizza.owl#VegetableTopping
No annotation properties
owl:Class
F
FishTopping
IRI
http://www.pizza-ontology.com/pizza.owl#FishTopping
No annotation properties
owl:Class
M
MozzarellaTopping
IRI
http://www.pizza-ontology.com/pizza.owl#MozzarellaTopping
No annotation properties
owl:Class
P
ParmesanTopping
IRI
http://www.pizza-ontology.com/pizza.owl#ParmesanTopping
No annotation properties
owl:Class
P
PeperoniSausageTopping
IRI
http://www.pizza-ontology.com/pizza.owl#PeperoniSausageTopping
No annotation properties
owl:Class
T
TomatoTopping
IRI
http://www.pizza-ontology.com/pizza.owl#TomatoTopping
No annotation properties
owl:Class
O
OliveTopping
IRI
http://www.pizza-ontology.com/pizza.owl#OliveTopping
No annotation properties
owl:Class
G
GarlicTopping
IRI
http://www.pizza-ontology.com/pizza.owl#GarlicTopping
No annotation properties
owl:Class
A
AnchoviesTopping
IRI
http://www.pizza-ontology.com/pizza.owl#AnchoviesTopping
No annotation properties
owl:ObjectProperty
H
hasTopping
IRI
http://www.pizza-ontology.com/pizza.owl#hasTopping
No annotation properties
owl:FunctionalProperty, owl:ObjectProperty
H
hasBase
IRI
http://www.pizza-ontology.com/pizza.owl#hasBase
No annotation properties
owl:ObjectProperty
I
isToppingOf
IRI
http://www.pizza-ontology.com/pizza.owl#isToppingOf
No annotation properties
owl:ObjectProperty
I
isBaseOf
IRI
http://www.pizza-ontology.com/pizza.owl#isBaseOf
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/10-tbox-equiv-class-axioms.svg b/docs/mcp-demo/pizza-tutorial/10-tbox-equiv-class-axioms.svg index 91902295..1253fe0a 100644 --- a/docs/mcp-demo/pizza-tutorial/10-tbox-equiv-class-axioms.svg +++ b/docs/mcp-demo/pizza-tutorial/10-tbox-equiv-class-axioms.svg @@ -1,6 +1,7 @@ -
owl:Class
P
Pizza
IRI
http://www.pizza-ontology.com/pizza.owl#Pizza
No annotation properties
owl:Class
P
PizzaBase
IRI
http://www.pizza-ontology.com/pizza.owl#PizzaBase
No annotation properties
owl:Class
P
PizzaTopping
IRI
http://www.pizza-ontology.com/pizza.owl#PizzaTopping
No annotation properties
owl:Class
T
ThinAndCrispyBase
IRI
http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase
No annotation properties
owl:Class
D
DeepPanBase
IRI
http://www.pizza-ontology.com/pizza.owl#DeepPanBase
No annotation properties
owl:Class
N
NamedPizza
IRI
http://www.pizza-ontology.com/pizza.owl#NamedPizza
No annotation properties
owl:Class
M
Margherita
IRI
http://www.pizza-ontology.com/pizza.owl#Margherita
owl:equivalentClass
urn:vg:bnode:bd82ce84
owl:Class
A
AmericanHot
IRI
http://www.pizza-ontology.com/pizza.owl#AmericanHot
owl:equivalentClass
urn:vg:bnode:be82d017
owl:Class
F
FruttiDiMare
IRI
http://www.pizza-ontology.com/pizza.owl#FruttiDiMare
owl:equivalentClass
urn:vg:bnode:bf82d1aa
owl:Class
C
CheeseTopping
IRI
http://www.pizza-ontology.com/pizza.owl#CheeseTopping
No annotation properties
owl:Class
M
MeatTopping
IRI
http://www.pizza-ontology.com/pizza.owl#MeatTopping
No annotation properties
owl:Class
V
VegetableTopping
IRI
http://www.pizza-ontology.com/pizza.owl#VegetableTopping
No annotation properties
owl:Class
F
FishTopping
IRI
http://www.pizza-ontology.com/pizza.owl#FishTopping
No annotation properties
owl:Class
M
MozzarellaTopping
IRI
http://www.pizza-ontology.com/pizza.owl#MozzarellaTopping
No annotation properties
owl:Class
P
ParmesanTopping
IRI
http://www.pizza-ontology.com/pizza.owl#ParmesanTopping
No annotation properties
owl:Class
P
PeperoniSausageTopping
IRI
http://www.pizza-ontology.com/pizza.owl#PeperoniSausageTopping
No annotation properties
owl:Class
T
TomatoTopping
IRI
http://www.pizza-ontology.com/pizza.owl#TomatoTopping
No annotation properties
owl:Class
O
OliveTopping
IRI
http://www.pizza-ontology.com/pizza.owl#OliveTopping
No annotation properties
owl:Class
G
GarlicTopping
IRI
http://www.pizza-ontology.com/pizza.owl#GarlicTopping
No annotation properties
owl:Class
A
AnchoviesTopping
IRI
http://www.pizza-ontology.com/pizza.owl#AnchoviesTopping
No annotation properties
owl:ObjectProperty
H
hasTopping
IRI
http://www.pizza-ontology.com/pizza.owl#hasTopping
No annotation properties
owl:FunctionalProperty, owl:ObjectProperty
H
hasBase
IRI
http://www.pizza-ontology.com/pizza.owl#hasBase
No annotation properties
owl:ObjectProperty
I
isToppingOf
IRI
http://www.pizza-ontology.com/pizza.owl#isToppingOf
No annotation properties
owl:ObjectProperty
I
isBaseOf
IRI
http://www.pizza-ontology.com/pizza.owl#isBaseOf
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/11-tbox-vegetarian-pizza.svg b/docs/mcp-demo/pizza-tutorial/11-tbox-vegetarian-pizza.svg index ae645104..29f078a2 100644 --- a/docs/mcp-demo/pizza-tutorial/11-tbox-vegetarian-pizza.svg +++ b/docs/mcp-demo/pizza-tutorial/11-tbox-vegetarian-pizza.svg @@ -1,6 +1,7 @@ -
owl:Class
P
Pizza
IRI
http://www.pizza-ontology.com/pizza.owl#Pizza
No annotation properties
owl:Class
P
PizzaBase
IRI
http://www.pizza-ontology.com/pizza.owl#PizzaBase
No annotation properties
owl:Class
P
PizzaTopping
IRI
http://www.pizza-ontology.com/pizza.owl#PizzaTopping
No annotation properties
owl:Class
T
ThinAndCrispyBase
IRI
http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase
No annotation properties
owl:Class
D
DeepPanBase
IRI
http://www.pizza-ontology.com/pizza.owl#DeepPanBase
No annotation properties
owl:Class
N
NamedPizza
IRI
http://www.pizza-ontology.com/pizza.owl#NamedPizza
No annotation properties
owl:Class
M
Margherita
IRI
http://www.pizza-ontology.com/pizza.owl#Margherita
owl:equivalentClass
urn:vg:bnode:bd82ce84
owl:Class
A
AmericanHot
IRI
http://www.pizza-ontology.com/pizza.owl#AmericanHot
owl:equivalentClass
urn:vg:bnode:be82d017
owl:Class
F
FruttiDiMare
IRI
http://www.pizza-ontology.com/pizza.owl#FruttiDiMare
owl:equivalentClass
urn:vg:bnode:bf82d1aa
owl:Class
C
CheeseTopping
IRI
http://www.pizza-ontology.com/pizza.owl#CheeseTopping
No annotation properties
owl:Class
M
MeatTopping
IRI
http://www.pizza-ontology.com/pizza.owl#MeatTopping
No annotation properties
owl:Class
V
VegetableTopping
IRI
http://www.pizza-ontology.com/pizza.owl#VegetableTopping
No annotation properties
owl:Class
F
FishTopping
IRI
http://www.pizza-ontology.com/pizza.owl#FishTopping
No annotation properties
owl:Class
M
MozzarellaTopping
IRI
http://www.pizza-ontology.com/pizza.owl#MozzarellaTopping
No annotation properties
owl:Class
P
ParmesanTopping
IRI
http://www.pizza-ontology.com/pizza.owl#ParmesanTopping
No annotation properties
owl:Class
P
PeperoniSausageTopping
IRI
http://www.pizza-ontology.com/pizza.owl#PeperoniSausageTopping
No annotation properties
owl:Class
T
TomatoTopping
IRI
http://www.pizza-ontology.com/pizza.owl#TomatoTopping
No annotation properties
owl:Class
O
OliveTopping
IRI
http://www.pizza-ontology.com/pizza.owl#OliveTopping
No annotation properties
owl:Class
G
GarlicTopping
IRI
http://www.pizza-ontology.com/pizza.owl#GarlicTopping
No annotation properties
owl:Class
A
AnchoviesTopping
IRI
http://www.pizza-ontology.com/pizza.owl#AnchoviesTopping
No annotation properties
owl:ObjectProperty
H
hasTopping
IRI
http://www.pizza-ontology.com/pizza.owl#hasTopping
No annotation properties
owl:FunctionalProperty, owl:ObjectProperty
H
hasBase
IRI
http://www.pizza-ontology.com/pizza.owl#hasBase
No annotation properties
owl:ObjectProperty
I
isToppingOf
IRI
http://www.pizza-ontology.com/pizza.owl#isToppingOf
No annotation properties
owl:ObjectProperty
I
isBaseOf
IRI
http://www.pizza-ontology.com/pizza.owl#isBaseOf
No annotation properties
owl:Class
V
VegetarianPizza
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/12-abox-untyped-pizzas.svg b/docs/mcp-demo/pizza-tutorial/12-abox-untyped-pizzas.svg index 2d6d6346..6bc0a9e1 100644 --- a/docs/mcp-demo/pizza-tutorial/12-abox-untyped-pizzas.svg +++ b/docs/mcp-demo/pizza-tutorial/12-abox-untyped-pizzas.svg @@ -1,6 +1,7 @@ -
owl:Restriction
U
urn:vg:bnode:bd82ce84
owl:Restriction
U
urn:vg:bnode:be82d017
owl:Restriction
U
urn:vg:bnode:bf82d1aa
owl:Restriction
U
urn:vg:bnode:e68f38e6
P
pizza1
P
pizza2
P
pizza3
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/13-pizza1-built.svg b/docs/mcp-demo/pizza-tutorial/13-pizza1-built.svg index 9943a39a..62880c80 100644 --- a/docs/mcp-demo/pizza-tutorial/13-pizza1-built.svg +++ b/docs/mcp-demo/pizza-tutorial/13-pizza1-built.svg @@ -1,6 +1,7 @@ -
owl:Restriction
U
urn:vg:bnode:bd82ce84
owl:Restriction
U
urn:vg:bnode:be82d017
owl:Restriction
U
urn:vg:bnode:bf82d1aa
owl:Restriction
U
urn:vg:bnode:e68f38e6
P
pizza1
P
pizza2
P
pizza3
pizza:MozzarellaTopping
M
mozz
pizza:TomatoTopping
T
tomato
pizza:ThinAndCrispyBase
T
thin & crispy
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/14-pizza2-built.svg b/docs/mcp-demo/pizza-tutorial/14-pizza2-built.svg index 329d73dc..63929734 100644 --- a/docs/mcp-demo/pizza-tutorial/14-pizza2-built.svg +++ b/docs/mcp-demo/pizza-tutorial/14-pizza2-built.svg @@ -1,6 +1,7 @@ -
owl:Restriction
U
urn:vg:bnode:bd82ce84
owl:Restriction
U
urn:vg:bnode:be82d017
owl:Restriction
U
urn:vg:bnode:bf82d1aa
owl:Restriction
U
urn:vg:bnode:e68f38e6
P
pizza1
P
pizza2
P
pizza3
pizza:MozzarellaTopping
M
mozz
pizza:TomatoTopping
T
tomato
pizza:ThinAndCrispyBase
T
thin & crispy
pizza:PeperoniSausageTopping
P
peperoni
pizza:MozzarellaTopping
M
mozz
pizza:OliveTopping
O
olive
pizza:DeepPanBase
D
deep pan
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/15-abox-before-reasoning.svg b/docs/mcp-demo/pizza-tutorial/15-abox-before-reasoning.svg index 233bc4b0..9e2401fc 100644 --- a/docs/mcp-demo/pizza-tutorial/15-abox-before-reasoning.svg +++ b/docs/mcp-demo/pizza-tutorial/15-abox-before-reasoning.svg @@ -1,6 +1,7 @@ -
owl:Restriction
U
urn:vg:bnode:bd82ce84
IRI
urn:vg:bnode:bd82ce84
No annotation properties
owl:Restriction
U
urn:vg:bnode:be82d017
IRI
urn:vg:bnode:be82d017
No annotation properties
owl:Restriction
U
urn:vg:bnode:bf82d1aa
IRI
urn:vg:bnode:bf82d1aa
No annotation properties
owl:Restriction
U
urn:vg:bnode:e68f38e6
IRI
urn:vg:bnode:e68f38e6
No annotation properties
P
pizza1
IRI
http://www.pizza-ontology.com/pizza.owl#pizza1
No annotation properties
P
pizza2
IRI
http://www.pizza-ontology.com/pizza.owl#pizza2
No annotation properties
P
pizza3
IRI
http://www.pizza-ontology.com/pizza.owl#pizza3
No annotation properties
pizza:MozzarellaTopping
M
mozz
IRI
http://www.pizza-ontology.com/pizza.owl#mozz1
No annotation properties
pizza:TomatoTopping
T
tomato
IRI
http://www.pizza-ontology.com/pizza.owl#tom1
No annotation properties
pizza:ThinAndCrispyBase
T
thin & crispy
IRI
http://www.pizza-ontology.com/pizza.owl#thin1
No annotation properties
pizza:PeperoniSausageTopping
P
peperoni
IRI
http://www.pizza-ontology.com/pizza.owl#pep1
No annotation properties
pizza:MozzarellaTopping
M
mozz
IRI
http://www.pizza-ontology.com/pizza.owl#mozz2
No annotation properties
pizza:OliveTopping
O
olive
IRI
http://www.pizza-ontology.com/pizza.owl#olive1
No annotation properties
pizza:DeepPanBase
D
deep pan
IRI
http://www.pizza-ontology.com/pizza.owl#deep1
No annotation properties
pizza:AnchoviesTopping
A
anchovies
IRI
http://www.pizza-ontology.com/pizza.owl#anch1
No annotation properties
pizza:GarlicTopping
G
garlic
IRI
http://www.pizza-ontology.com/pizza.owl#garlic1
No annotation properties
pizza:ThinAndCrispyBase
T
thin & crispy
IRI
http://www.pizza-ontology.com/pizza.owl#thin2
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/16-pizza4-built.svg b/docs/mcp-demo/pizza-tutorial/16-pizza4-built.svg index 6453ceb2..5f916a64 100644 --- a/docs/mcp-demo/pizza-tutorial/16-pizza4-built.svg +++ b/docs/mcp-demo/pizza-tutorial/16-pizza4-built.svg @@ -1,6 +1,7 @@ -
owl:Restriction
U
urn:vg:bnode:bd82ce84
IRI
urn:vg:bnode:bd82ce84
No annotation properties
owl:Restriction
U
urn:vg:bnode:be82d017
IRI
urn:vg:bnode:be82d017
No annotation properties
owl:Restriction
U
urn:vg:bnode:bf82d1aa
IRI
urn:vg:bnode:bf82d1aa
No annotation properties
owl:Restriction
U
urn:vg:bnode:e68f38e6
IRI
urn:vg:bnode:e68f38e6
No annotation properties
P
pizza1
IRI
http://www.pizza-ontology.com/pizza.owl#pizza1
No annotation properties
P
pizza2
IRI
http://www.pizza-ontology.com/pizza.owl#pizza2
No annotation properties
P
pizza3
IRI
http://www.pizza-ontology.com/pizza.owl#pizza3
No annotation properties
pizza:MozzarellaTopping
M
mozz
IRI
http://www.pizza-ontology.com/pizza.owl#mozz1
No annotation properties
pizza:TomatoTopping
T
tomato
IRI
http://www.pizza-ontology.com/pizza.owl#tom1
No annotation properties
pizza:ThinAndCrispyBase
T
thin & crispy
IRI
http://www.pizza-ontology.com/pizza.owl#thin1
No annotation properties
pizza:PeperoniSausageTopping
P
peperoni
IRI
http://www.pizza-ontology.com/pizza.owl#pep1
No annotation properties
pizza:MozzarellaTopping
M
mozz
IRI
http://www.pizza-ontology.com/pizza.owl#mozz2
No annotation properties
pizza:OliveTopping
O
olive
IRI
http://www.pizza-ontology.com/pizza.owl#olive1
No annotation properties
pizza:DeepPanBase
D
deep pan
IRI
http://www.pizza-ontology.com/pizza.owl#deep1
No annotation properties
pizza:AnchoviesTopping
A
anchovies
IRI
http://www.pizza-ontology.com/pizza.owl#anch1
No annotation properties
pizza:GarlicTopping
G
garlic
IRI
http://www.pizza-ontology.com/pizza.owl#garlic1
No annotation properties
pizza:ThinAndCrispyBase
T
thin & crispy
IRI
http://www.pizza-ontology.com/pizza.owl#thin2
No annotation properties
P
pizza4
IRI
http://www.pizza-ontology.com/pizza.owl#pizza4
No annotation properties
pizza:OliveTopping
O
olive
IRI
http://www.pizza-ontology.com/pizza.owl#olive2
No annotation properties
pizza:TomatoTopping
T
tomato
IRI
http://www.pizza-ontology.com/pizza.owl#tom2
No annotation properties
pizza:ThinAndCrispyBase
T
thin & crispy
IRI
http://www.pizza-ontology.com/pizza.owl#thin3
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/17-after-reasoning.svg b/docs/mcp-demo/pizza-tutorial/17-after-reasoning.svg index b8a63680..d6443874 100644 --- a/docs/mcp-demo/pizza-tutorial/17-after-reasoning.svg +++ b/docs/mcp-demo/pizza-tutorial/17-after-reasoning.svg @@ -1,6 +1,7 @@ -
owl:Restriction
U
urn:vg:bnode:bd82ce84
IRI
urn:vg:bnode:bd82ce84
rdfs:subClassOf
urn:vg:bnode:bd82ce84
urn:vg:bnode:e68f38e6
owl:equivalentClass
urn:vg:bnode:bd82ce84
owl:Restriction
U
urn:vg:bnode:be82d017
IRI
urn:vg:bnode:be82d017
rdfs:subClassOf
urn:vg:bnode:be82d017
owl:equivalentClass
urn:vg:bnode:be82d017
owl:Restriction
U
urn:vg:bnode:bf82d1aa
IRI
urn:vg:bnode:bf82d1aa
rdfs:subClassOf
urn:vg:bnode:bf82d1aa
owl:equivalentClass
urn:vg:bnode:bf82d1aa
owl:Restriction
U
urn:vg:bnode:e68f38e6
IRI
urn:vg:bnode:e68f38e6
rdfs:subClassOf
urn:vg:bnode:e68f38e6
owl:equivalentClass
urn:vg:bnode:e68f38e6
pizza:Margherita, pizza:NamedPizza, pizza:Pizza, pizza:VegetarianPizza
P
pizza1
IRI
http://www.pizza-ontology.com/pizza.owl#pizza1
No annotation properties
pizza:AmericanHot, pizza:NamedPizza, pizza:Pizza, pizza:VegetarianPizza
P
pizza2
IRI
http://www.pizza-ontology.com/pizza.owl#pizza2
No annotation properties
pizza:FruttiDiMare, pizza:NamedPizza, pizza:Pizza, pizza:VegetarianPizza
P
pizza3
IRI
http://www.pizza-ontology.com/pizza.owl#pizza3
No annotation properties
pizza:CheeseTopping, pizza:MozzarellaTopping, pizza:PizzaTopping
M
mozz
IRI
http://www.pizza-ontology.com/pizza.owl#mozz1
No annotation properties
pizza:PizzaTopping, pizza:TomatoTopping, pizza:VegetableTopping
T
tomato
IRI
http://www.pizza-ontology.com/pizza.owl#tom1
No annotation properties
pizza:PizzaBase, pizza:ThinAndCrispyBase
T
thin & crispy
IRI
http://www.pizza-ontology.com/pizza.owl#thin1
No annotation properties
pizza:MeatTopping, pizza:PeperoniSausageTopping, pizza:PizzaTopping
P
peperoni
IRI
http://www.pizza-ontology.com/pizza.owl#pep1
No annotation properties
pizza:CheeseTopping, pizza:MozzarellaTopping, pizza:PizzaTopping
M
mozz
IRI
http://www.pizza-ontology.com/pizza.owl#mozz2
No annotation properties
pizza:OliveTopping, pizza:PizzaTopping, pizza:VegetableTopping
O
olive
IRI
http://www.pizza-ontology.com/pizza.owl#olive1
No annotation properties
pizza:DeepPanBase, pizza:PizzaBase
D
deep pan
IRI
http://www.pizza-ontology.com/pizza.owl#deep1
No annotation properties
pizza:AnchoviesTopping, pizza:FishTopping, pizza:PizzaTopping
A
anchovies
IRI
http://www.pizza-ontology.com/pizza.owl#anch1
No annotation properties
pizza:GarlicTopping, pizza:PizzaTopping, pizza:VegetableTopping
G
garlic
IRI
http://www.pizza-ontology.com/pizza.owl#garlic1
No annotation properties
pizza:PizzaBase, pizza:ThinAndCrispyBase
T
thin & crispy
IRI
http://www.pizza-ontology.com/pizza.owl#thin2
No annotation properties
pizza:Margherita, pizza:NamedPizza, pizza:Pizza, pizza:VegetarianPizza
P
pizza4
IRI
http://www.pizza-ontology.com/pizza.owl#pizza4
No annotation properties
pizza:OliveTopping, pizza:PizzaTopping, pizza:VegetableTopping
O
olive
IRI
http://www.pizza-ontology.com/pizza.owl#olive2
No annotation properties
pizza:PizzaTopping, pizza:TomatoTopping, pizza:VegetableTopping
T
tomato
IRI
http://www.pizza-ontology.com/pizza.owl#tom2
No annotation properties
pizza:PizzaBase, pizza:ThinAndCrispyBase
T
thin & crispy
IRI
http://www.pizza-ontology.com/pizza.owl#thin3
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/18-pizza1-inferred.svg b/docs/mcp-demo/pizza-tutorial/18-pizza1-inferred.svg index a25f46cc..6b07194a 100644 --- a/docs/mcp-demo/pizza-tutorial/18-pizza1-inferred.svg +++ b/docs/mcp-demo/pizza-tutorial/18-pizza1-inferred.svg @@ -1,6 +1,7 @@ -
owl:Restriction
U
urn:vg:bnode:bd82ce84
IRI
urn:vg:bnode:bd82ce84
rdfs:subClassOf
urn:vg:bnode:bd82ce84
urn:vg:bnode:e68f38e6
owl:equivalentClass
urn:vg:bnode:bd82ce84
owl:Restriction
U
urn:vg:bnode:be82d017
IRI
urn:vg:bnode:be82d017
rdfs:subClassOf
urn:vg:bnode:be82d017
owl:equivalentClass
urn:vg:bnode:be82d017
owl:Restriction
U
urn:vg:bnode:bf82d1aa
IRI
urn:vg:bnode:bf82d1aa
rdfs:subClassOf
urn:vg:bnode:bf82d1aa
owl:equivalentClass
urn:vg:bnode:bf82d1aa
owl:Restriction
U
urn:vg:bnode:e68f38e6
IRI
urn:vg:bnode:e68f38e6
rdfs:subClassOf
urn:vg:bnode:e68f38e6
owl:equivalentClass
urn:vg:bnode:e68f38e6
pizza:Margherita, pizza:NamedPizza, pizza:Pizza, pizza:VegetarianPizza
P
pizza1
IRI
http://www.pizza-ontology.com/pizza.owl#pizza1
No annotation properties
pizza:AmericanHot, pizza:NamedPizza, pizza:Pizza, pizza:VegetarianPizza
P
pizza2
IRI
http://www.pizza-ontology.com/pizza.owl#pizza2
No annotation properties
pizza:FruttiDiMare, pizza:NamedPizza, pizza:Pizza, pizza:VegetarianPizza
P
pizza3
IRI
http://www.pizza-ontology.com/pizza.owl#pizza3
No annotation properties
pizza:CheeseTopping, pizza:MozzarellaTopping, pizza:PizzaTopping
M
mozz
IRI
http://www.pizza-ontology.com/pizza.owl#mozz1
No annotation properties
pizza:PizzaTopping, pizza:TomatoTopping, pizza:VegetableTopping
T
tomato
IRI
http://www.pizza-ontology.com/pizza.owl#tom1
No annotation properties
pizza:PizzaBase, pizza:ThinAndCrispyBase
T
thin & crispy
IRI
http://www.pizza-ontology.com/pizza.owl#thin1
No annotation properties
pizza:MeatTopping, pizza:PeperoniSausageTopping, pizza:PizzaTopping
P
peperoni
IRI
http://www.pizza-ontology.com/pizza.owl#pep1
No annotation properties
pizza:CheeseTopping, pizza:MozzarellaTopping, pizza:PizzaTopping
M
mozz
IRI
http://www.pizza-ontology.com/pizza.owl#mozz2
No annotation properties
pizza:OliveTopping, pizza:PizzaTopping, pizza:VegetableTopping
O
olive
IRI
http://www.pizza-ontology.com/pizza.owl#olive1
No annotation properties
pizza:DeepPanBase, pizza:PizzaBase
D
deep pan
IRI
http://www.pizza-ontology.com/pizza.owl#deep1
No annotation properties
pizza:AnchoviesTopping, pizza:FishTopping, pizza:PizzaTopping
A
anchovies
IRI
http://www.pizza-ontology.com/pizza.owl#anch1
No annotation properties
pizza:GarlicTopping, pizza:PizzaTopping, pizza:VegetableTopping
G
garlic
IRI
http://www.pizza-ontology.com/pizza.owl#garlic1
No annotation properties
pizza:PizzaBase, pizza:ThinAndCrispyBase
T
thin & crispy
IRI
http://www.pizza-ontology.com/pizza.owl#thin2
No annotation properties
pizza:Margherita, pizza:NamedPizza, pizza:Pizza, pizza:VegetarianPizza
P
pizza4
IRI
http://www.pizza-ontology.com/pizza.owl#pizza4
No annotation properties
pizza:OliveTopping, pizza:PizzaTopping, pizza:VegetableTopping
O
olive
IRI
http://www.pizza-ontology.com/pizza.owl#olive2
No annotation properties
pizza:PizzaTopping, pizza:TomatoTopping, pizza:VegetableTopping
T
tomato
IRI
http://www.pizza-ontology.com/pizza.owl#tom2
No annotation properties
pizza:PizzaBase, pizza:ThinAndCrispyBase
T
thin & crispy
IRI
http://www.pizza-ontology.com/pizza.owl#thin3
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/19-mozz1-inferred.svg b/docs/mcp-demo/pizza-tutorial/19-mozz1-inferred.svg index 524e768a..a621f64a 100644 --- a/docs/mcp-demo/pizza-tutorial/19-mozz1-inferred.svg +++ b/docs/mcp-demo/pizza-tutorial/19-mozz1-inferred.svg @@ -1,6 +1,7 @@ -
owl:Restriction
U
urn:vg:bnode:bd82ce84
IRI
urn:vg:bnode:bd82ce84
rdfs:subClassOf
urn:vg:bnode:bd82ce84
urn:vg:bnode:e68f38e6
owl:equivalentClass
urn:vg:bnode:bd82ce84
owl:Restriction
U
urn:vg:bnode:be82d017
IRI
urn:vg:bnode:be82d017
rdfs:subClassOf
urn:vg:bnode:be82d017
owl:equivalentClass
urn:vg:bnode:be82d017
owl:Restriction
U
urn:vg:bnode:bf82d1aa
IRI
urn:vg:bnode:bf82d1aa
rdfs:subClassOf
urn:vg:bnode:bf82d1aa
owl:equivalentClass
urn:vg:bnode:bf82d1aa
owl:Restriction
U
urn:vg:bnode:e68f38e6
IRI
urn:vg:bnode:e68f38e6
rdfs:subClassOf
urn:vg:bnode:e68f38e6
owl:equivalentClass
urn:vg:bnode:e68f38e6
pizza:Margherita, pizza:NamedPizza, pizza:Pizza, pizza:VegetarianPizza
P
pizza1
IRI
http://www.pizza-ontology.com/pizza.owl#pizza1
No annotation properties
pizza:AmericanHot, pizza:NamedPizza, pizza:Pizza, pizza:VegetarianPizza
P
pizza2
IRI
http://www.pizza-ontology.com/pizza.owl#pizza2
No annotation properties
pizza:FruttiDiMare, pizza:NamedPizza, pizza:Pizza, pizza:VegetarianPizza
P
pizza3
IRI
http://www.pizza-ontology.com/pizza.owl#pizza3
No annotation properties
pizza:CheeseTopping, pizza:MozzarellaTopping, pizza:PizzaTopping
M
mozz
IRI
http://www.pizza-ontology.com/pizza.owl#mozz1
No annotation properties
pizza:PizzaTopping, pizza:TomatoTopping, pizza:VegetableTopping
T
tomato
IRI
http://www.pizza-ontology.com/pizza.owl#tom1
No annotation properties
pizza:PizzaBase, pizza:ThinAndCrispyBase
T
thin & crispy
IRI
http://www.pizza-ontology.com/pizza.owl#thin1
No annotation properties
pizza:MeatTopping, pizza:PeperoniSausageTopping, pizza:PizzaTopping
P
peperoni
IRI
http://www.pizza-ontology.com/pizza.owl#pep1
No annotation properties
pizza:CheeseTopping, pizza:MozzarellaTopping, pizza:PizzaTopping
M
mozz
IRI
http://www.pizza-ontology.com/pizza.owl#mozz2
No annotation properties
pizza:OliveTopping, pizza:PizzaTopping, pizza:VegetableTopping
O
olive
IRI
http://www.pizza-ontology.com/pizza.owl#olive1
No annotation properties
pizza:DeepPanBase, pizza:PizzaBase
D
deep pan
IRI
http://www.pizza-ontology.com/pizza.owl#deep1
No annotation properties
pizza:AnchoviesTopping, pizza:FishTopping, pizza:PizzaTopping
A
anchovies
IRI
http://www.pizza-ontology.com/pizza.owl#anch1
No annotation properties
pizza:GarlicTopping, pizza:PizzaTopping, pizza:VegetableTopping
G
garlic
IRI
http://www.pizza-ontology.com/pizza.owl#garlic1
No annotation properties
pizza:PizzaBase, pizza:ThinAndCrispyBase
T
thin & crispy
IRI
http://www.pizza-ontology.com/pizza.owl#thin2
No annotation properties
pizza:Margherita, pizza:NamedPizza, pizza:Pizza, pizza:VegetarianPizza
P
pizza4
IRI
http://www.pizza-ontology.com/pizza.owl#pizza4
No annotation properties
pizza:OliveTopping, pizza:PizzaTopping, pizza:VegetableTopping
O
olive
IRI
http://www.pizza-ontology.com/pizza.owl#olive2
No annotation properties
pizza:PizzaTopping, pizza:TomatoTopping, pizza:VegetableTopping
T
tomato
IRI
http://www.pizza-ontology.com/pizza.owl#tom2
No annotation properties
pizza:PizzaBase, pizza:ThinAndCrispyBase
T
thin & crispy
IRI
http://www.pizza-ontology.com/pizza.owl#thin3
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/pizza-tutorial/20-owa-vegetarian-lesson.svg b/docs/mcp-demo/pizza-tutorial/20-owa-vegetarian-lesson.svg index 8408026a..e608a643 100644 --- a/docs/mcp-demo/pizza-tutorial/20-owa-vegetarian-lesson.svg +++ b/docs/mcp-demo/pizza-tutorial/20-owa-vegetarian-lesson.svg @@ -1,6 +1,7 @@ -
owl:Restriction
U
urn:vg:bnode:bd82ce84
IRI
urn:vg:bnode:bd82ce84
rdfs:subClassOf
urn:vg:bnode:bd82ce84
urn:vg:bnode:e68f38e6
owl:equivalentClass
urn:vg:bnode:bd82ce84
owl:Restriction
U
urn:vg:bnode:be82d017
IRI
urn:vg:bnode:be82d017
rdfs:subClassOf
urn:vg:bnode:be82d017
owl:equivalentClass
urn:vg:bnode:be82d017
owl:Restriction
U
urn:vg:bnode:bf82d1aa
IRI
urn:vg:bnode:bf82d1aa
rdfs:subClassOf
urn:vg:bnode:bf82d1aa
owl:equivalentClass
urn:vg:bnode:bf82d1aa
owl:Restriction
U
urn:vg:bnode:e68f38e6
IRI
urn:vg:bnode:e68f38e6
rdfs:subClassOf
urn:vg:bnode:e68f38e6
owl:equivalentClass
urn:vg:bnode:e68f38e6
pizza:Margherita, pizza:NamedPizza, pizza:Pizza, pizza:VegetarianPizza
P
pizza1
IRI
http://www.pizza-ontology.com/pizza.owl#pizza1
No annotation properties
pizza:AmericanHot, pizza:NamedPizza, pizza:Pizza, pizza:VegetarianPizza
P
pizza2
IRI
http://www.pizza-ontology.com/pizza.owl#pizza2
No annotation properties
pizza:FruttiDiMare, pizza:NamedPizza, pizza:Pizza, pizza:VegetarianPizza
P
pizza3
IRI
http://www.pizza-ontology.com/pizza.owl#pizza3
No annotation properties
pizza:CheeseTopping, pizza:MozzarellaTopping, pizza:PizzaTopping
M
mozz
IRI
http://www.pizza-ontology.com/pizza.owl#mozz1
No annotation properties
pizza:PizzaTopping, pizza:TomatoTopping, pizza:VegetableTopping
T
tomato
IRI
http://www.pizza-ontology.com/pizza.owl#tom1
No annotation properties
pizza:PizzaBase, pizza:ThinAndCrispyBase
T
thin & crispy
IRI
http://www.pizza-ontology.com/pizza.owl#thin1
No annotation properties
pizza:MeatTopping, pizza:PeperoniSausageTopping, pizza:PizzaTopping
P
peperoni
IRI
http://www.pizza-ontology.com/pizza.owl#pep1
No annotation properties
pizza:CheeseTopping, pizza:MozzarellaTopping, pizza:PizzaTopping
M
mozz
IRI
http://www.pizza-ontology.com/pizza.owl#mozz2
No annotation properties
pizza:OliveTopping, pizza:PizzaTopping, pizza:VegetableTopping
O
olive
IRI
http://www.pizza-ontology.com/pizza.owl#olive1
No annotation properties
pizza:DeepPanBase, pizza:PizzaBase
D
deep pan
IRI
http://www.pizza-ontology.com/pizza.owl#deep1
No annotation properties
pizza:AnchoviesTopping, pizza:FishTopping, pizza:PizzaTopping
A
anchovies
IRI
http://www.pizza-ontology.com/pizza.owl#anch1
No annotation properties
pizza:GarlicTopping, pizza:PizzaTopping, pizza:VegetableTopping
G
garlic
IRI
http://www.pizza-ontology.com/pizza.owl#garlic1
No annotation properties
pizza:PizzaBase, pizza:ThinAndCrispyBase
T
thin & crispy
IRI
http://www.pizza-ontology.com/pizza.owl#thin2
No annotation properties
pizza:Margherita, pizza:NamedPizza, pizza:Pizza, pizza:VegetarianPizza
P
pizza4
IRI
http://www.pizza-ontology.com/pizza.owl#pizza4
No annotation properties
pizza:OliveTopping, pizza:PizzaTopping, pizza:VegetableTopping
O
olive
IRI
http://www.pizza-ontology.com/pizza.owl#olive2
No annotation properties
pizza:PizzaTopping, pizza:TomatoTopping, pizza:VegetableTopping
T
tomato
IRI
http://www.pizza-ontology.com/pizza.owl#tom2
No annotation properties
pizza:PizzaBase, pizza:ThinAndCrispyBase
T
thin & crispy
IRI
http://www.pizza-ontology.com/pizza.owl#thin3
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/reasoning-demo.md b/docs/mcp-demo/reasoning-demo.md index 3083e1c5..717f381a 100644 --- a/docs/mcp-demo/reasoning-demo.md +++ b/docs/mcp-demo/reasoning-demo.md @@ -76,50 +76,50 @@ Canvas: 10 nodes, 0 links
22 tool calls ✓ -`{"jsonrpc":"2.0","id":13,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#Employee","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/reasoning-demo#Person"}}}` -`{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#Manager","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/reasoning-demo#Employee"}}}` -`{"jsonrpc":"2.0","id":15,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#Executive","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/reasoning-demo#Manager"}}}` -`{"jsonrpc":"2.0","id":16,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasFriend","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subPropertyOf","objectIri":"http://example.com/reasoning-demo#knows"}}}` -`{"jsonrpc":"2.0","id":17,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#isManagedBy","predicateIri":"http://www.w3.org/2002/07/owl#inverseOf","objectIri":"http://example.com/reasoning-demo#manages"}}}` -`{"jsonrpc":"2.0","id":18,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#isColleagueOf","predicateIri":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","objectIri":"http://www.w3.org/2002/07/owl#SymmetricProperty"}}}` -`{"jsonrpc":"2.0","id":19,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasSupervisor","predicateIri":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","objectIri":"http://www.w3.org/2002/07/owl#TransitiveProperty"}}}` -`{"jsonrpc":"2.0","id":21,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#knows","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Person"}}}` -`{"jsonrpc":"2.0","id":22,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#knows","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Person"}}}` -`{"jsonrpc":"2.0","id":23,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasFriend","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Person"}}}` -`{"jsonrpc":"2.0","id":24,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasFriend","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Person"}}}` -`{"jsonrpc":"2.0","id":25,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#manages","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Manager"}}}` -`{"jsonrpc":"2.0","id":26,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#manages","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Employee"}}}` -`{"jsonrpc":"2.0","id":27,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#isManagedBy","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Employee"}}}` -`{"jsonrpc":"2.0","id":28,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#isManagedBy","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Manager"}}}` -`{"jsonrpc":"2.0","id":29,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#isColleagueOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Employee"}}}` -`{"jsonrpc":"2.0","id":30,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#isColleagueOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Employee"}}}` -`{"jsonrpc":"2.0","id":31,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasSupervisor","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Employee"}}}` -`{"jsonrpc":"2.0","id":32,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasSupervisor","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Manager"}}}` +`{"jsonrpc":"2.0","id":13,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#Employee","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/reasoning-demo#Person"}}}` +`{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#Manager","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/reasoning-demo#Employee"}}}` +`{"jsonrpc":"2.0","id":15,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#Executive","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/reasoning-demo#Manager"}}}` +`{"jsonrpc":"2.0","id":16,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasFriend","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subPropertyOf","objectIri":"http://example.com/reasoning-demo#knows"}}}` +`{"jsonrpc":"2.0","id":17,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#isManagedBy","predicateIri":"http://www.w3.org/2002/07/owl#inverseOf","objectIri":"http://example.com/reasoning-demo#manages"}}}` +`{"jsonrpc":"2.0","id":18,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#isColleagueOf","predicateIri":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","objectIri":"http://www.w3.org/2002/07/owl#SymmetricProperty"}}}` +`{"jsonrpc":"2.0","id":19,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasSupervisor","predicateIri":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","objectIri":"http://www.w3.org/2002/07/owl#TransitiveProperty"}}}` +`{"jsonrpc":"2.0","id":21,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#knows","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Person"}}}` +`{"jsonrpc":"2.0","id":22,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#knows","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Person"}}}` +`{"jsonrpc":"2.0","id":23,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasFriend","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Person"}}}` +`{"jsonrpc":"2.0","id":24,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasFriend","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Person"}}}` +`{"jsonrpc":"2.0","id":25,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#manages","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Manager"}}}` +`{"jsonrpc":"2.0","id":26,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#manages","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Employee"}}}` +`{"jsonrpc":"2.0","id":27,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#isManagedBy","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Employee"}}}` +`{"jsonrpc":"2.0","id":28,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#isManagedBy","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Manager"}}}` +`{"jsonrpc":"2.0","id":29,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#isColleagueOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Employee"}}}` +`{"jsonrpc":"2.0","id":30,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#isColleagueOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Employee"}}}` +`{"jsonrpc":"2.0","id":31,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasSupervisor","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Employee"}}}` +`{"jsonrpc":"2.0","id":32,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasSupervisor","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Manager"}}}` `{"jsonrpc":"2.0","id":34,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":200}}}` `{"jsonrpc":"2.0","id":35,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":36,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":200}}}` ```tool-result [Ontosphere — 22 tools ✓] -✓ addLink: s=Employee p=subClassOf o=Person -✓ addLink: s=Manager p=subClassOf o=Employee -✓ addLink: s=Executive p=subClassOf o=Manager -✓ addLink: s=hasFriend p=subPropertyOf o=knows -✓ addLink: s=isManagedBy p=inverseOf o=manages -✓ addLink: s=isColleagueOf p=type o=SymmetricProperty -✓ addLink: s=hasSupervisor p=type o=TransitiveProperty -✓ addLink: s=knows p=domain o=Person -✓ addLink: s=knows p=range o=Person -✓ addLink: s=hasFriend p=domain o=Person -✓ addLink: s=hasFriend p=range o=Person -✓ addLink: s=manages p=domain o=Manager -✓ addLink: s=manages p=range o=Employee -✓ addLink: s=isManagedBy p=domain o=Employee -✓ addLink: s=isManagedBy p=range o=Manager -✓ addLink: s=isColleagueOf p=domain o=Employee -✓ addLink: s=isColleagueOf p=range o=Employee -✓ addLink: s=hasSupervisor p=domain o=Employee -✓ addLink: s=hasSupervisor p=range o=Manager +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple ✓ runLayout: elk-layered ✓ expandNode ✓ runLayout: elk-layered @@ -174,24 +174,24 @@ can see the ABox shape before we run the reasoner:
9 tool calls ✓ -`{"jsonrpc":"2.0","id":43,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#alice","predicateIri":"http://example.com/reasoning-demo#manages","objectIri":"http://example.com/reasoning-demo#carol"}}}` -`{"jsonrpc":"2.0","id":44,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#dave","predicateIri":"http://example.com/reasoning-demo#manages","objectIri":"http://example.com/reasoning-demo#bob"}}}` -`{"jsonrpc":"2.0","id":45,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#alice","predicateIri":"http://example.com/reasoning-demo#hasFriend","objectIri":"http://example.com/reasoning-demo#bob"}}}` -`{"jsonrpc":"2.0","id":46,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#bob","predicateIri":"http://example.com/reasoning-demo#isColleagueOf","objectIri":"http://example.com/reasoning-demo#carol"}}}` -`{"jsonrpc":"2.0","id":47,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#carol","predicateIri":"http://example.com/reasoning-demo#hasSupervisor","objectIri":"http://example.com/reasoning-demo#bob"}}}` -`{"jsonrpc":"2.0","id":48,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#bob","predicateIri":"http://example.com/reasoning-demo#hasSupervisor","objectIri":"http://example.com/reasoning-demo#alice"}}}` +`{"jsonrpc":"2.0","id":43,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#alice","predicateIri":"http://example.com/reasoning-demo#manages","objectIri":"http://example.com/reasoning-demo#carol"}}}` +`{"jsonrpc":"2.0","id":44,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#dave","predicateIri":"http://example.com/reasoning-demo#manages","objectIri":"http://example.com/reasoning-demo#bob"}}}` +`{"jsonrpc":"2.0","id":45,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#alice","predicateIri":"http://example.com/reasoning-demo#hasFriend","objectIri":"http://example.com/reasoning-demo#bob"}}}` +`{"jsonrpc":"2.0","id":46,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#bob","predicateIri":"http://example.com/reasoning-demo#isColleagueOf","objectIri":"http://example.com/reasoning-demo#carol"}}}` +`{"jsonrpc":"2.0","id":47,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#carol","predicateIri":"http://example.com/reasoning-demo#hasSupervisor","objectIri":"http://example.com/reasoning-demo#bob"}}}` +`{"jsonrpc":"2.0","id":48,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#bob","predicateIri":"http://example.com/reasoning-demo#hasSupervisor","objectIri":"http://example.com/reasoning-demo#alice"}}}` `{"jsonrpc":"2.0","id":49,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":200}}}` `{"jsonrpc":"2.0","id":50,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":51,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":200}}}` ```tool-result [Ontosphere — 9 tools ✓] -✓ addLink: s=alice p=manages o=carol -✓ addLink: s=dave p=manages o=bob -✓ addLink: s=alice p=hasFriend o=bob -✓ addLink: s=bob p=isColleagueOf o=carol -✓ addLink: s=carol p=hasSupervisor o=bob -✓ addLink: s=bob p=hasSupervisor o=alice +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple ✓ runLayout: dagre-lr ✓ expandNode ✓ runLayout: dagre-lr diff --git a/docs/mcp-demo/reasoning-demo/01-tbox.svg b/docs/mcp-demo/reasoning-demo/01-tbox.svg index e5b399e4..406051c8 100644 --- a/docs/mcp-demo/reasoning-demo/01-tbox.svg +++ b/docs/mcp-demo/reasoning-demo/01-tbox.svg @@ -1,6 +1,7 @@
owl:Class
P
Person
IRI
http://example.com/reasoning-demo#Person
No annotation properties
owl:Class
E
Employee
IRI
http://example.com/reasoning-demo#Employee
No annotation properties
owl:Class
M
Manager
IRI
http://example.com/reasoning-demo#Manager
No annotation properties
owl:Class
E
Executive
IRI
http://example.com/reasoning-demo#Executive
No annotation properties
owl:ObjectProperty
K
knows
IRI
http://example.com/reasoning-demo#knows
No annotation properties
owl:ObjectProperty
H
hasFriend
IRI
http://example.com/reasoning-demo#hasFriend
No annotation properties
owl:ObjectProperty
M
manages
IRI
http://example.com/reasoning-demo#manages
No annotation properties
owl:ObjectProperty
I
isManagedBy
IRI
http://example.com/reasoning-demo#isManagedBy
No annotation properties
owl:ObjectProperty, owl:SymmetricProperty
I
isColleagueOf
IRI
http://example.com/reasoning-demo#isColleagueOf
No annotation properties
owl:ObjectProperty, owl:TransitiveProperty
H
hasSupervisor
IRI
http://example.com/reasoning-demo#hasSupervisor
No annotation properties
\ No newline at end of file +* { border-color: var(--border); }
owl:Class
P
Person
IRI
http://example.com/reasoning-demo#Person
No annotation properties
owl:Class
E
Employee
IRI
http://example.com/reasoning-demo#Employee
No annotation properties
owl:Class
M
Manager
IRI
http://example.com/reasoning-demo#Manager
No annotation properties
owl:Class
E
Executive
IRI
http://example.com/reasoning-demo#Executive
No annotation properties
owl:ObjectProperty
K
knows
IRI
http://example.com/reasoning-demo#knows
No annotation properties
owl:ObjectProperty
H
hasFriend
IRI
http://example.com/reasoning-demo#hasFriend
No annotation properties
owl:ObjectProperty
M
manages
IRI
http://example.com/reasoning-demo#manages
No annotation properties
owl:ObjectProperty
I
isManagedBy
IRI
http://example.com/reasoning-demo#isManagedBy
No annotation properties
owl:ObjectProperty, owl:SymmetricProperty
I
isColleagueOf
IRI
http://example.com/reasoning-demo#isColleagueOf
No annotation properties
owl:ObjectProperty, owl:TransitiveProperty
H
hasSupervisor
IRI
http://example.com/reasoning-demo#hasSupervisor
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/reasoning-demo/02-before-reasoning.svg b/docs/mcp-demo/reasoning-demo/02-before-reasoning.svg index 6dadcdf9..bfde9440 100644 --- a/docs/mcp-demo/reasoning-demo/02-before-reasoning.svg +++ b/docs/mcp-demo/reasoning-demo/02-before-reasoning.svg @@ -1,6 +1,7 @@
Executive
A
Alice
IRI
http://example.com/reasoning-demo#alice
hasFriend
http://example.com/reasoning-demo#bob
manages
http://example.com/reasoning-demo#carol
B
Bob
IRI
http://example.com/reasoning-demo#bob
isColleagueOf
http://example.com/reasoning-demo#carol
C
Carol
IRI
http://example.com/reasoning-demo#carol
hasSupervisor
http://example.com/reasoning-demo#bob
D
Dave
IRI
http://example.com/reasoning-demo#dave
manages
http://example.com/reasoning-demo#bob
\ No newline at end of file +* { border-color: var(--border); }
Executive
A
Alice
IRI
http://example.com/reasoning-demo#alice
hasFriend
http://example.com/reasoning-demo#bob
manages
http://example.com/reasoning-demo#carol
B
Bob
IRI
http://example.com/reasoning-demo#bob
isColleagueOf
http://example.com/reasoning-demo#carol
C
Carol
IRI
http://example.com/reasoning-demo#carol
hasSupervisor
http://example.com/reasoning-demo#bob
D
Dave
IRI
http://example.com/reasoning-demo#dave
manages
http://example.com/reasoning-demo#bob
\ No newline at end of file diff --git a/docs/mcp-demo/reasoning-demo/03-after-reasoning.svg b/docs/mcp-demo/reasoning-demo/03-after-reasoning.svg index 3f1248f5..72edff31 100644 --- a/docs/mcp-demo/reasoning-demo/03-after-reasoning.svg +++ b/docs/mcp-demo/reasoning-demo/03-after-reasoning.svg @@ -1,6 +1,7 @@
Employee, Executive, Manager, Person
A
Alice
IRI
http://example.com/reasoning-demo#alice
No annotation properties
Employee, Manager, Person
B
Bob
IRI
http://example.com/reasoning-demo#bob
No annotation properties
Employee, Person
C
Carol
IRI
http://example.com/reasoning-demo#carol
No annotation properties
Employee, Manager, Person
D
Dave
IRI
http://example.com/reasoning-demo#dave
No annotation properties
\ No newline at end of file +* { border-color: var(--border); }
Employee, Executive, Manager, Person
A
Alice
IRI
http://example.com/reasoning-demo#alice
No annotation properties
Employee, Manager, Person
B
Bob
IRI
http://example.com/reasoning-demo#bob
No annotation properties
Employee, Person
C
Carol
IRI
http://example.com/reasoning-demo#carol
No annotation properties
Employee, Manager, Person
D
Dave
IRI
http://example.com/reasoning-demo#dave
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/reasoning-demo/04-dave-focus.svg b/docs/mcp-demo/reasoning-demo/04-dave-focus.svg index 90312cca..dc87b5b4 100644 --- a/docs/mcp-demo/reasoning-demo/04-dave-focus.svg +++ b/docs/mcp-demo/reasoning-demo/04-dave-focus.svg @@ -1,6 +1,7 @@
Employee, Executive, Manager, Person
A
Alice
IRI
http://example.com/reasoning-demo#alice
No annotation properties
Employee, Manager, Person
B
Bob
IRI
http://example.com/reasoning-demo#bob
No annotation properties
Employee, Person
C
Carol
IRI
http://example.com/reasoning-demo#carol
No annotation properties
Employee, Manager, Person
D
Dave
IRI
http://example.com/reasoning-demo#dave
No annotation properties
\ No newline at end of file +* { border-color: var(--border); }
Employee, Executive, Manager, Person
A
Alice
IRI
http://example.com/reasoning-demo#alice
No annotation properties
Employee, Manager, Person
B
Bob
IRI
http://example.com/reasoning-demo#bob
No annotation properties
Employee, Person
C
Carol
IRI
http://example.com/reasoning-demo#carol
No annotation properties
Employee, Manager, Person
D
Dave
IRI
http://example.com/reasoning-demo#dave
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/scene-ontology.md b/docs/mcp-demo/scene-ontology.md index 4a7448b0..6f179da9 100644 --- a/docs/mcp-demo/scene-ontology.md +++ b/docs/mcp-demo/scene-ontology.md @@ -31,21 +31,21 @@ node then wiring the subclass chain and property axioms one by one. No bulk load `{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://purl.obolibrary.org/obo/RO_0000057","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"has participant"}}}` `{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://purl.obolibrary.org/obo/RO_0000066","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"occurs in"}}}` `{"jsonrpc":"2.0","id":13,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://purl.obolibrary.org/obo/RO_0000087","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"has role"}}}` -`{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000002","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000001"}}}` -`{"jsonrpc":"2.0","id":15,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000003","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000001"}}}` -`{"jsonrpc":"2.0","id":16,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000004","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000002"}}}` -`{"jsonrpc":"2.0","id":17,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000015","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000003"}}}` -`{"jsonrpc":"2.0","id":18,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000017","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000002"}}}` -`{"jsonrpc":"2.0","id":19,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000023","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000017"}}}` -`{"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000029","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000004"}}}` -`{"jsonrpc":"2.0","id":21,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000055","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://purl.obolibrary.org/obo/BFO_0000015"}}}` -`{"jsonrpc":"2.0","id":22,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000055","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://purl.obolibrary.org/obo/BFO_0000017"}}}` -`{"jsonrpc":"2.0","id":23,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000057","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://purl.obolibrary.org/obo/BFO_0000015"}}}` -`{"jsonrpc":"2.0","id":24,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000057","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://purl.obolibrary.org/obo/BFO_0000004"}}}` -`{"jsonrpc":"2.0","id":25,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000066","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://purl.obolibrary.org/obo/BFO_0000015"}}}` -`{"jsonrpc":"2.0","id":26,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000066","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://purl.obolibrary.org/obo/BFO_0000029"}}}` -`{"jsonrpc":"2.0","id":27,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000087","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://purl.obolibrary.org/obo/BFO_0000004"}}}` -`{"jsonrpc":"2.0","id":28,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000087","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://purl.obolibrary.org/obo/BFO_0000023"}}}` +`{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000002","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000001"}}}` +`{"jsonrpc":"2.0","id":15,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000003","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000001"}}}` +`{"jsonrpc":"2.0","id":16,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000004","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000002"}}}` +`{"jsonrpc":"2.0","id":17,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000015","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000003"}}}` +`{"jsonrpc":"2.0","id":18,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000017","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000002"}}}` +`{"jsonrpc":"2.0","id":19,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000023","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000017"}}}` +`{"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000029","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000004"}}}` +`{"jsonrpc":"2.0","id":21,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000055","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://purl.obolibrary.org/obo/BFO_0000015"}}}` +`{"jsonrpc":"2.0","id":22,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000055","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://purl.obolibrary.org/obo/BFO_0000017"}}}` +`{"jsonrpc":"2.0","id":23,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000057","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://purl.obolibrary.org/obo/BFO_0000015"}}}` +`{"jsonrpc":"2.0","id":24,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000057","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://purl.obolibrary.org/obo/BFO_0000004"}}}` +`{"jsonrpc":"2.0","id":25,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000066","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://purl.obolibrary.org/obo/BFO_0000015"}}}` +`{"jsonrpc":"2.0","id":26,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000066","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://purl.obolibrary.org/obo/BFO_0000029"}}}` +`{"jsonrpc":"2.0","id":27,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000087","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://purl.obolibrary.org/obo/BFO_0000004"}}}` +`{"jsonrpc":"2.0","id":28,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000087","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://purl.obolibrary.org/obo/BFO_0000023"}}}` ```tool-result [Ontosphere — 28 tools ✓] @@ -62,21 +62,21 @@ node then wiring the subclass chain and property axioms one by one. No bulk load ✓ addNode: http://purl.obolibrary.org/obo/RO_0000057 ✓ addNode: http://purl.obolibrary.org/obo/RO_0000066 ✓ addNode: http://purl.obolibrary.org/obo/RO_0000087 -✓ addLink: s=BFO_0000002 p=subClassOf o=BFO_0000001 -✓ addLink: s=BFO_0000003 p=subClassOf o=BFO_0000001 -✓ addLink: s=BFO_0000004 p=subClassOf o=BFO_0000002 -✓ addLink: s=BFO_0000015 p=subClassOf o=BFO_0000003 -✓ addLink: s=BFO_0000017 p=subClassOf o=BFO_0000002 -✓ addLink: s=BFO_0000023 p=subClassOf o=BFO_0000017 -✓ addLink: s=BFO_0000029 p=subClassOf o=BFO_0000004 -✓ addLink: s=BFO_0000055 p=domain o=BFO_0000015 -✓ addLink: s=BFO_0000055 p=range o=BFO_0000017 -✓ addLink: s=RO_0000057 p=domain o=BFO_0000015 -✓ addLink: s=RO_0000057 p=range o=BFO_0000004 -✓ addLink: s=RO_0000066 p=domain o=BFO_0000015 -✓ addLink: s=RO_0000066 p=range o=BFO_0000029 -✓ addLink: s=RO_0000087 p=domain o=BFO_0000004 -✓ addLink: s=RO_0000087 p=range o=BFO_0000023 +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple Canvas: 12 nodes, 15 links ``` @@ -98,11 +98,11 @@ Canvas: 12 nodes, 15 links `{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.com/scene-ontology#DialogueScene","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"DialogueScene"}}}` `{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.com/scene-ontology#ChaseScene","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"ChaseScene"}}}` `{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.com/scene-ontology#ConfrontationScene","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"ConfrontationScene"}}}` -`{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#Scene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000015"}}}` -`{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#ActionScene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#Scene"}}}` -`{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#DialogueScene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#Scene"}}}` -`{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#ChaseScene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#ActionScene"}}}` -`{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#ConfrontationScene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#ActionScene"}}}` +`{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#Scene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000015"}}}` +`{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#ActionScene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#Scene"}}}` +`{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#DialogueScene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#Scene"}}}` +`{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#ChaseScene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#ActionScene"}}}` +`{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#ConfrontationScene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#ActionScene"}}}` ```tool-result [Ontosphere — 10 tools ✓] @@ -111,11 +111,11 @@ Canvas: 12 nodes, 15 links ✓ addNode: http://example.com/scene-ontology#DialogueScene ✓ addNode: http://example.com/scene-ontology#ChaseScene ✓ addNode: http://example.com/scene-ontology#ConfrontationScene -✓ addLink: s=Scene p=subClassOf o=BFO_0000015 -✓ addLink: s=ActionScene p=subClassOf o=Scene -✓ addLink: s=DialogueScene p=subClassOf o=Scene -✓ addLink: s=ChaseScene p=subClassOf o=ActionScene -✓ addLink: s=ConfrontationScene p=subClassOf o=ActionScene +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple Canvas: 17 nodes, 20 links ``` @@ -136,10 +136,10 @@ Canvas: 17 nodes, 20 links `{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.com/scene-ontology#Location","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"Location"}}}` `{"jsonrpc":"2.0","id":15,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.com/scene-ontology#ExteriorLocation","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"ExteriorLocation"}}}` `{"jsonrpc":"2.0","id":16,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.com/scene-ontology#InteriorLocation","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"InteriorLocation"}}}` -`{"jsonrpc":"2.0","id":17,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#Character","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000004"}}}` -`{"jsonrpc":"2.0","id":18,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#Location","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000029"}}}` -`{"jsonrpc":"2.0","id":19,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#ExteriorLocation","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#Location"}}}` -`{"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#InteriorLocation","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#Location"}}}` +`{"jsonrpc":"2.0","id":17,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#Character","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000004"}}}` +`{"jsonrpc":"2.0","id":18,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#Location","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000029"}}}` +`{"jsonrpc":"2.0","id":19,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#ExteriorLocation","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#Location"}}}` +`{"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#InteriorLocation","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#Location"}}}` ```tool-result [Ontosphere — 8 tools ✓] @@ -147,10 +147,10 @@ Canvas: 17 nodes, 20 links ✓ addNode: http://example.com/scene-ontology#Location ✓ addNode: http://example.com/scene-ontology#ExteriorLocation ✓ addNode: http://example.com/scene-ontology#InteriorLocation -✓ addLink: s=Character p=subClassOf o=BFO_0000004 -✓ addLink: s=Location p=subClassOf o=BFO_0000029 -✓ addLink: s=ExteriorLocation p=subClassOf o=Location -✓ addLink: s=InteriorLocation p=subClassOf o=Location +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple Canvas: 21 nodes, 24 links ``` @@ -171,16 +171,16 @@ Canvas: 21 nodes, 24 links `{"jsonrpc":"2.0","id":22,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.com/scene-ontology#ProtagonistRole","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"ProtagonistRole"}}}` `{"jsonrpc":"2.0","id":23,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.com/scene-ontology#AntagonistRole","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"AntagonistRole"}}}` `{"jsonrpc":"2.0","id":24,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.com/scene-ontology#SupportingRole","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"SupportingRole"}}}` -`{"jsonrpc":"2.0","id":25,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#SceneRole","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000023"}}}` -`{"jsonrpc":"2.0","id":26,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#ProtagonistRole","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#SceneRole"}}}` -`{"jsonrpc":"2.0","id":27,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#AntagonistRole","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#SceneRole"}}}` -`{"jsonrpc":"2.0","id":28,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#SupportingRole","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#SceneRole"}}}` -`{"jsonrpc":"2.0","id":29,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000087","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/scene-ontology#Character"}}}` -`{"jsonrpc":"2.0","id":30,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000087","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/scene-ontology#SceneRole"}}}` -`{"jsonrpc":"2.0","id":31,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000057","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/scene-ontology#Scene"}}}` -`{"jsonrpc":"2.0","id":32,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000057","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/scene-ontology#Character"}}}` -`{"jsonrpc":"2.0","id":33,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000066","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/scene-ontology#Scene"}}}` -`{"jsonrpc":"2.0","id":34,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000066","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/scene-ontology#Location"}}}` +`{"jsonrpc":"2.0","id":25,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#SceneRole","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000023"}}}` +`{"jsonrpc":"2.0","id":26,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#ProtagonistRole","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#SceneRole"}}}` +`{"jsonrpc":"2.0","id":27,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#AntagonistRole","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#SceneRole"}}}` +`{"jsonrpc":"2.0","id":28,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#SupportingRole","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#SceneRole"}}}` +`{"jsonrpc":"2.0","id":29,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000087","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/scene-ontology#Character"}}}` +`{"jsonrpc":"2.0","id":30,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000087","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/scene-ontology#SceneRole"}}}` +`{"jsonrpc":"2.0","id":31,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000057","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/scene-ontology#Scene"}}}` +`{"jsonrpc":"2.0","id":32,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000057","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/scene-ontology#Character"}}}` +`{"jsonrpc":"2.0","id":33,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000066","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/scene-ontology#Scene"}}}` +`{"jsonrpc":"2.0","id":34,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000066","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/scene-ontology#Location"}}}` `{"jsonrpc":"2.0","id":35,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":200}}}` `{"jsonrpc":"2.0","id":36,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":37,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":200}}}` @@ -191,16 +191,16 @@ Canvas: 21 nodes, 24 links ✓ addNode: http://example.com/scene-ontology#ProtagonistRole ✓ addNode: http://example.com/scene-ontology#AntagonistRole ✓ addNode: http://example.com/scene-ontology#SupportingRole -✓ addLink: s=SceneRole p=subClassOf o=BFO_0000023 -✓ addLink: s=ProtagonistRole p=subClassOf o=SceneRole -✓ addLink: s=AntagonistRole p=subClassOf o=SceneRole -✓ addLink: s=SupportingRole p=subClassOf o=SceneRole -✓ addLink: s=RO_0000087 p=domain o=Character -✓ addLink: s=RO_0000087 p=range o=SceneRole -✓ addLink: s=RO_0000057 p=domain o=Scene -✓ addLink: s=RO_0000057 p=range o=Character -✓ addLink: s=RO_0000066 p=domain o=Scene -✓ addLink: s=RO_0000066 p=range o=Location +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple ✓ runLayout: elk-layered ✓ expandNode ✓ runLayout: elk-layered @@ -265,36 +265,36 @@ Canvas: 10 nodes, 0 links
15 tool calls ✓ -`{"jsonrpc":"2.0","id":49,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/RO_0000057","objectIri":"http://example.com/scene-ontology#jake"}}}` -`{"jsonrpc":"2.0","id":50,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/RO_0000057","objectIri":"http://example.com/scene-ontology#vera"}}}` -`{"jsonrpc":"2.0","id":51,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/RO_0000066","objectIri":"http://example.com/scene-ontology#rooftop"}}}` -`{"jsonrpc":"2.0","id":52,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#cafeDialogue","predicateIri":"http://purl.obolibrary.org/obo/RO_0000057","objectIri":"http://example.com/scene-ontology#jake"}}}` -`{"jsonrpc":"2.0","id":53,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#cafeDialogue","predicateIri":"http://purl.obolibrary.org/obo/RO_0000057","objectIri":"http://example.com/scene-ontology#sam"}}}` -`{"jsonrpc":"2.0","id":54,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#cafeDialogue","predicateIri":"http://purl.obolibrary.org/obo/RO_0000066","objectIri":"http://example.com/scene-ontology#cafe"}}}` -`{"jsonrpc":"2.0","id":55,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#jake","predicateIri":"http://purl.obolibrary.org/obo/RO_0000087","objectIri":"http://example.com/scene-ontology#jakeProtagonistRole"}}}` -`{"jsonrpc":"2.0","id":56,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#vera","predicateIri":"http://purl.obolibrary.org/obo/RO_0000087","objectIri":"http://example.com/scene-ontology#veraAntagonistRole"}}}` -`{"jsonrpc":"2.0","id":57,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#sam","predicateIri":"http://purl.obolibrary.org/obo/RO_0000087","objectIri":"http://example.com/scene-ontology#samSupportingRole"}}}` -`{"jsonrpc":"2.0","id":58,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/BFO_0000055","objectIri":"http://example.com/scene-ontology#jakeProtagonistRole"}}}` -`{"jsonrpc":"2.0","id":59,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/BFO_0000055","objectIri":"http://example.com/scene-ontology#veraAntagonistRole"}}}` -`{"jsonrpc":"2.0","id":60,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#cafeDialogue","predicateIri":"http://purl.obolibrary.org/obo/BFO_0000055","objectIri":"http://example.com/scene-ontology#samSupportingRole"}}}` +`{"jsonrpc":"2.0","id":49,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/RO_0000057","objectIri":"http://example.com/scene-ontology#jake"}}}` +`{"jsonrpc":"2.0","id":50,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/RO_0000057","objectIri":"http://example.com/scene-ontology#vera"}}}` +`{"jsonrpc":"2.0","id":51,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/RO_0000066","objectIri":"http://example.com/scene-ontology#rooftop"}}}` +`{"jsonrpc":"2.0","id":52,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#cafeDialogue","predicateIri":"http://purl.obolibrary.org/obo/RO_0000057","objectIri":"http://example.com/scene-ontology#jake"}}}` +`{"jsonrpc":"2.0","id":53,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#cafeDialogue","predicateIri":"http://purl.obolibrary.org/obo/RO_0000057","objectIri":"http://example.com/scene-ontology#sam"}}}` +`{"jsonrpc":"2.0","id":54,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#cafeDialogue","predicateIri":"http://purl.obolibrary.org/obo/RO_0000066","objectIri":"http://example.com/scene-ontology#cafe"}}}` +`{"jsonrpc":"2.0","id":55,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#jake","predicateIri":"http://purl.obolibrary.org/obo/RO_0000087","objectIri":"http://example.com/scene-ontology#jakeProtagonistRole"}}}` +`{"jsonrpc":"2.0","id":56,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#vera","predicateIri":"http://purl.obolibrary.org/obo/RO_0000087","objectIri":"http://example.com/scene-ontology#veraAntagonistRole"}}}` +`{"jsonrpc":"2.0","id":57,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#sam","predicateIri":"http://purl.obolibrary.org/obo/RO_0000087","objectIri":"http://example.com/scene-ontology#samSupportingRole"}}}` +`{"jsonrpc":"2.0","id":58,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/BFO_0000055","objectIri":"http://example.com/scene-ontology#jakeProtagonistRole"}}}` +`{"jsonrpc":"2.0","id":59,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/BFO_0000055","objectIri":"http://example.com/scene-ontology#veraAntagonistRole"}}}` +`{"jsonrpc":"2.0","id":60,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#cafeDialogue","predicateIri":"http://purl.obolibrary.org/obo/BFO_0000055","objectIri":"http://example.com/scene-ontology#samSupportingRole"}}}` `{"jsonrpc":"2.0","id":61,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":200}}}` `{"jsonrpc":"2.0","id":62,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":63,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":200}}}` ```tool-result [Ontosphere — 15 tools ✓] -✓ addLink: s=rooftopChase p=RO_0000057 o=jake -✓ addLink: s=rooftopChase p=RO_0000057 o=vera -✓ addLink: s=rooftopChase p=RO_0000066 o=rooftop -✓ addLink: s=cafeDialogue p=RO_0000057 o=jake -✓ addLink: s=cafeDialogue p=RO_0000057 o=sam -✓ addLink: s=cafeDialogue p=RO_0000066 o=cafe -✓ addLink: s=jake p=RO_0000087 o=jakeProtagonistRole -✓ addLink: s=vera p=RO_0000087 o=veraAntagonistRole -✓ addLink: s=sam p=RO_0000087 o=samSupportingRole -✓ addLink: s=rooftopChase p=BFO_0000055 o=jakeProtagonistRole -✓ addLink: s=rooftopChase p=BFO_0000055 o=veraAntagonistRole -✓ addLink: s=cafeDialogue p=BFO_0000055 o=samSupportingRole +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple +✓ addTriple ✓ runLayout: dagre-lr ✓ expandNode ✓ runLayout: dagre-lr @@ -384,11 +384,11 @@ Canvas: 10 nodes, 12 links @prefix rdfs: . @prefix xml: . @prefix xsd: . -@prefix : . @prefix spw: . @prefix dcterms: . @prefix p-plan: . @prefix prov: . +@prefix : . @prefix terms: . @prefix vann: . @prefix dtype: . diff --git a/docs/mcp-demo/scene-ontology/01-tbox.svg b/docs/mcp-demo/scene-ontology/01-tbox.svg index 16c8b5c1..2f1a620a 100644 --- a/docs/mcp-demo/scene-ontology/01-tbox.svg +++ b/docs/mcp-demo/scene-ontology/01-tbox.svg @@ -1,6 +1,7 @@
owl:Class
E
entity
IRI
http://purl.obolibrary.org/obo/BFO_0000001
No annotation properties
owl:Class
C
continuant
IRI
http://purl.obolibrary.org/obo/BFO_0000002
No annotation properties
owl:Class
O
occurrent
IRI
http://purl.obolibrary.org/obo/BFO_0000003
No annotation properties
owl:Class
I
independent continuant
IRI
http://purl.obolibrary.org/obo/BFO_0000004
No annotation properties
owl:Class
P
process
IRI
http://purl.obolibrary.org/obo/BFO_0000015
No annotation properties
owl:Class
R
realizable entity
IRI
http://purl.obolibrary.org/obo/BFO_0000017
No annotation properties
owl:Class
R
role
IRI
http://purl.obolibrary.org/obo/BFO_0000023
No annotation properties
owl:Class
S
site
IRI
http://purl.obolibrary.org/obo/BFO_0000029
No annotation properties
owl:ObjectProperty
R
realizes
IRI
http://purl.obolibrary.org/obo/BFO_0000055
No annotation properties
owl:ObjectProperty
H
has participant
IRI
http://purl.obolibrary.org/obo/RO_0000057
No annotation properties
owl:ObjectProperty
O
occurs in
IRI
http://purl.obolibrary.org/obo/RO_0000066
No annotation properties
owl:ObjectProperty
H
has role
IRI
http://purl.obolibrary.org/obo/RO_0000087
No annotation properties
owl:Class
S
Scene
IRI
http://example.com/scene-ontology#Scene
No annotation properties
owl:Class
A
ActionScene
IRI
http://example.com/scene-ontology#ActionScene
No annotation properties
owl:Class
D
DialogueScene
IRI
http://example.com/scene-ontology#DialogueScene
No annotation properties
owl:Class
C
ChaseScene
IRI
http://example.com/scene-ontology#ChaseScene
No annotation properties
owl:Class
C
ConfrontationScene
IRI
http://example.com/scene-ontology#ConfrontationScene
No annotation properties
owl:Class
C
Character
IRI
http://example.com/scene-ontology#Character
No annotation properties
owl:Class
L
Location
IRI
http://example.com/scene-ontology#Location
No annotation properties
owl:Class
E
ExteriorLocation
IRI
http://example.com/scene-ontology#ExteriorLocation
No annotation properties
owl:Class
I
InteriorLocation
IRI
http://example.com/scene-ontology#InteriorLocation
No annotation properties
owl:Class
S
SceneRole
IRI
http://example.com/scene-ontology#SceneRole
No annotation properties
owl:Class
P
ProtagonistRole
IRI
http://example.com/scene-ontology#ProtagonistRole
No annotation properties
owl:Class
A
AntagonistRole
IRI
http://example.com/scene-ontology#AntagonistRole
No annotation properties
owl:Class
S
SupportingRole
IRI
http://example.com/scene-ontology#SupportingRole
No annotation properties
\ No newline at end of file +* { border-color: var(--border); }
owl:Class
E
entity
IRI
http://purl.obolibrary.org/obo/BFO_0000001
No annotation properties
owl:Class
C
continuant
IRI
http://purl.obolibrary.org/obo/BFO_0000002
No annotation properties
owl:Class
O
occurrent
IRI
http://purl.obolibrary.org/obo/BFO_0000003
No annotation properties
owl:Class
I
independent continuant
IRI
http://purl.obolibrary.org/obo/BFO_0000004
No annotation properties
owl:Class
P
process
IRI
http://purl.obolibrary.org/obo/BFO_0000015
No annotation properties
owl:Class
R
realizable entity
IRI
http://purl.obolibrary.org/obo/BFO_0000017
No annotation properties
owl:Class
R
role
IRI
http://purl.obolibrary.org/obo/BFO_0000023
No annotation properties
owl:Class
S
site
IRI
http://purl.obolibrary.org/obo/BFO_0000029
No annotation properties
owl:ObjectProperty
R
realizes
IRI
http://purl.obolibrary.org/obo/BFO_0000055
No annotation properties
owl:ObjectProperty
H
has participant
IRI
http://purl.obolibrary.org/obo/RO_0000057
No annotation properties
owl:ObjectProperty
O
occurs in
IRI
http://purl.obolibrary.org/obo/RO_0000066
No annotation properties
owl:ObjectProperty
H
has role
IRI
http://purl.obolibrary.org/obo/RO_0000087
No annotation properties
owl:Class
S
Scene
IRI
http://example.com/scene-ontology#Scene
No annotation properties
owl:Class
A
ActionScene
IRI
http://example.com/scene-ontology#ActionScene
No annotation properties
owl:Class
D
DialogueScene
IRI
http://example.com/scene-ontology#DialogueScene
No annotation properties
owl:Class
C
ChaseScene
IRI
http://example.com/scene-ontology#ChaseScene
No annotation properties
owl:Class
C
ConfrontationScene
IRI
http://example.com/scene-ontology#ConfrontationScene
No annotation properties
owl:Class
C
Character
IRI
http://example.com/scene-ontology#Character
No annotation properties
owl:Class
L
Location
IRI
http://example.com/scene-ontology#Location
No annotation properties
owl:Class
E
ExteriorLocation
IRI
http://example.com/scene-ontology#ExteriorLocation
No annotation properties
owl:Class
I
InteriorLocation
IRI
http://example.com/scene-ontology#InteriorLocation
No annotation properties
owl:Class
S
SceneRole
IRI
http://example.com/scene-ontology#SceneRole
No annotation properties
owl:Class
P
ProtagonistRole
IRI
http://example.com/scene-ontology#ProtagonistRole
No annotation properties
owl:Class
A
AntagonistRole
IRI
http://example.com/scene-ontology#AntagonistRole
No annotation properties
owl:Class
S
SupportingRole
IRI
http://example.com/scene-ontology#SupportingRole
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/scene-ontology/02-abox-full.svg b/docs/mcp-demo/scene-ontology/02-abox-full.svg index d3a97ca3..bc83693d 100644 --- a/docs/mcp-demo/scene-ontology/02-abox-full.svg +++ b/docs/mcp-demo/scene-ontology/02-abox-full.svg @@ -1,6 +1,7 @@
ExteriorLocation
R
Rooftop
IRI
http://example.com/scene-ontology#rooftop
No annotation properties
InteriorLocation
C
Café
IRI
http://example.com/scene-ontology#cafe
No annotation properties
J
Jake
IRI
http://example.com/scene-ontology#jake
No annotation properties
V
Vera
IRI
http://example.com/scene-ontology#vera
No annotation properties
S
Sam
IRI
http://example.com/scene-ontology#sam
No annotation properties
ChaseScene
R
Rooftop Chase
IRI
http://example.com/scene-ontology#rooftopChase
RO_0000057
http://example.com/scene-ontology#jake
http://example.com/scene-ontology#vera
DialogueScene
C
Café Dialogue
IRI
http://example.com/scene-ontology#cafeDialogue
RO_0000057
http://example.com/scene-ontology#jake
http://example.com/scene-ontology#sam
ProtagonistRole
J
Jake — Protagonist
IRI
http://example.com/scene-ontology#jakeProtagonistRole
No annotation properties
AntagonistRole
V
Vera — Antagonist
IRI
http://example.com/scene-ontology#veraAntagonistRole
No annotation properties
SupportingRole
S
Sam — Supporting
IRI
http://example.com/scene-ontology#samSupportingRole
No annotation properties
\ No newline at end of file +* { border-color: var(--border); }
ExteriorLocation
R
Rooftop
IRI
http://example.com/scene-ontology#rooftop
No annotation properties
InteriorLocation
C
Café
IRI
http://example.com/scene-ontology#cafe
No annotation properties
J
Jake
IRI
http://example.com/scene-ontology#jake
No annotation properties
V
Vera
IRI
http://example.com/scene-ontology#vera
No annotation properties
S
Sam
IRI
http://example.com/scene-ontology#sam
No annotation properties
ChaseScene
R
Rooftop Chase
IRI
http://example.com/scene-ontology#rooftopChase
RO_0000057
http://example.com/scene-ontology#jake
http://example.com/scene-ontology#vera
DialogueScene
C
Café Dialogue
IRI
http://example.com/scene-ontology#cafeDialogue
RO_0000057
http://example.com/scene-ontology#jake
http://example.com/scene-ontology#sam
ProtagonistRole
J
Jake — Protagonist
IRI
http://example.com/scene-ontology#jakeProtagonistRole
No annotation properties
AntagonistRole
V
Vera — Antagonist
IRI
http://example.com/scene-ontology#veraAntagonistRole
No annotation properties
SupportingRole
S
Sam — Supporting
IRI
http://example.com/scene-ontology#samSupportingRole
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/scene-ontology/03-after-reasoning.svg b/docs/mcp-demo/scene-ontology/03-after-reasoning.svg index 4d54ad45..cd10fbe8 100644 --- a/docs/mcp-demo/scene-ontology/03-after-reasoning.svg +++ b/docs/mcp-demo/scene-ontology/03-after-reasoning.svg @@ -1,6 +1,7 @@
ExteriorLocation, Location, BFO_0000001, BFO_0000002, BFO_0000004, BFO_0000029
R
Rooftop
IRI
http://example.com/scene-ontology#rooftop
No annotation properties
InteriorLocation, Location, BFO_0000001, BFO_0000002, BFO_0000004, BFO_0000029
C
Café
IRI
http://example.com/scene-ontology#cafe
No annotation properties
Character, BFO_0000001, BFO_0000002, BFO_0000004
J
Jake
IRI
http://example.com/scene-ontology#jake
No annotation properties
Character, BFO_0000001, BFO_0000002, BFO_0000004
V
Vera
IRI
http://example.com/scene-ontology#vera
No annotation properties
Character, BFO_0000001, BFO_0000002, BFO_0000004
S
Sam
IRI
http://example.com/scene-ontology#sam
No annotation properties
ActionScene, ChaseScene, Scene, BFO_0000001, BFO_0000003, BFO_0000015
R
Rooftop Chase
IRI
http://example.com/scene-ontology#rooftopChase
No annotation properties
DialogueScene, Scene, BFO_0000001, BFO_0000003, BFO_0000015
C
Café Dialogue
IRI
http://example.com/scene-ontology#cafeDialogue
No annotation properties
ProtagonistRole, SceneRole, BFO_0000001, BFO_0000002, BFO_0000017, BFO_0000023
J
Jake — Protagonist
IRI
http://example.com/scene-ontology#jakeProtagonistRole
No annotation properties
AntagonistRole, SceneRole, BFO_0000001, BFO_0000002, BFO_0000017, BFO_0000023
V
Vera — Antagonist
IRI
http://example.com/scene-ontology#veraAntagonistRole
No annotation properties
SceneRole, SupportingRole, BFO_0000001, BFO_0000002, BFO_0000017, BFO_0000023
S
Sam — Supporting
IRI
http://example.com/scene-ontology#samSupportingRole
No annotation properties
\ No newline at end of file +* { border-color: var(--border); }
ExteriorLocation, Location, BFO_0000001, BFO_0000002, BFO_0000004, BFO_0000029
R
Rooftop
IRI
http://example.com/scene-ontology#rooftop
No annotation properties
InteriorLocation, Location, BFO_0000001, BFO_0000002, BFO_0000004, BFO_0000029
C
Café
IRI
http://example.com/scene-ontology#cafe
No annotation properties
Character, BFO_0000001, BFO_0000002, BFO_0000004
J
Jake
IRI
http://example.com/scene-ontology#jake
No annotation properties
Character, BFO_0000001, BFO_0000002, BFO_0000004
V
Vera
IRI
http://example.com/scene-ontology#vera
No annotation properties
Character, BFO_0000001, BFO_0000002, BFO_0000004
S
Sam
IRI
http://example.com/scene-ontology#sam
No annotation properties
ActionScene, ChaseScene, Scene, BFO_0000001, BFO_0000003, BFO_0000015
R
Rooftop Chase
IRI
http://example.com/scene-ontology#rooftopChase
No annotation properties
DialogueScene, Scene, BFO_0000001, BFO_0000003, BFO_0000015
C
Café Dialogue
IRI
http://example.com/scene-ontology#cafeDialogue
No annotation properties
ProtagonistRole, SceneRole, BFO_0000001, BFO_0000002, BFO_0000017, BFO_0000023
J
Jake — Protagonist
IRI
http://example.com/scene-ontology#jakeProtagonistRole
No annotation properties
AntagonistRole, SceneRole, BFO_0000001, BFO_0000002, BFO_0000017, BFO_0000023
V
Vera — Antagonist
IRI
http://example.com/scene-ontology#veraAntagonistRole
No annotation properties
SceneRole, SupportingRole, BFO_0000001, BFO_0000002, BFO_0000017, BFO_0000023
S
Sam — Supporting
IRI
http://example.com/scene-ontology#samSupportingRole
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/scene-ontology/04-jake-focus.svg b/docs/mcp-demo/scene-ontology/04-jake-focus.svg index 7b3d386a..a820b84e 100644 --- a/docs/mcp-demo/scene-ontology/04-jake-focus.svg +++ b/docs/mcp-demo/scene-ontology/04-jake-focus.svg @@ -1,6 +1,7 @@
ExteriorLocation, Location, BFO_0000001, BFO_0000002, BFO_0000004, BFO_0000029
R
Rooftop
IRI
http://example.com/scene-ontology#rooftop
No annotation properties
InteriorLocation, Location, BFO_0000001, BFO_0000002, BFO_0000004, BFO_0000029
C
Café
IRI
http://example.com/scene-ontology#cafe
No annotation properties
Character, BFO_0000001, BFO_0000002, BFO_0000004
J
Jake
IRI
http://example.com/scene-ontology#jake
No annotation properties
Character, BFO_0000001, BFO_0000002, BFO_0000004
V
Vera
IRI
http://example.com/scene-ontology#vera
No annotation properties
Character, BFO_0000001, BFO_0000002, BFO_0000004
S
Sam
IRI
http://example.com/scene-ontology#sam
No annotation properties
ActionScene, ChaseScene, Scene, BFO_0000001, BFO_0000003, BFO_0000015
R
Rooftop Chase
IRI
http://example.com/scene-ontology#rooftopChase
No annotation properties
DialogueScene, Scene, BFO_0000001, BFO_0000003, BFO_0000015
C
Café Dialogue
IRI
http://example.com/scene-ontology#cafeDialogue
No annotation properties
ProtagonistRole, SceneRole, BFO_0000001, BFO_0000002, BFO_0000017, BFO_0000023
J
Jake — Protagonist
IRI
http://example.com/scene-ontology#jakeProtagonistRole
No annotation properties
AntagonistRole, SceneRole, BFO_0000001, BFO_0000002, BFO_0000017, BFO_0000023
V
Vera — Antagonist
IRI
http://example.com/scene-ontology#veraAntagonistRole
No annotation properties
SceneRole, SupportingRole, BFO_0000001, BFO_0000002, BFO_0000017, BFO_0000023
S
Sam — Supporting
IRI
http://example.com/scene-ontology#samSupportingRole
No annotation properties
\ No newline at end of file +* { border-color: var(--border); }
ExteriorLocation, Location, BFO_0000001, BFO_0000002, BFO_0000004, BFO_0000029
R
Rooftop
IRI
http://example.com/scene-ontology#rooftop
No annotation properties
InteriorLocation, Location, BFO_0000001, BFO_0000002, BFO_0000004, BFO_0000029
C
Café
IRI
http://example.com/scene-ontology#cafe
No annotation properties
Character, BFO_0000001, BFO_0000002, BFO_0000004
J
Jake
IRI
http://example.com/scene-ontology#jake
No annotation properties
Character, BFO_0000001, BFO_0000002, BFO_0000004
V
Vera
IRI
http://example.com/scene-ontology#vera
No annotation properties
Character, BFO_0000001, BFO_0000002, BFO_0000004
S
Sam
IRI
http://example.com/scene-ontology#sam
No annotation properties
ActionScene, ChaseScene, Scene, BFO_0000001, BFO_0000003, BFO_0000015
R
Rooftop Chase
IRI
http://example.com/scene-ontology#rooftopChase
No annotation properties
DialogueScene, Scene, BFO_0000001, BFO_0000003, BFO_0000015
C
Café Dialogue
IRI
http://example.com/scene-ontology#cafeDialogue
No annotation properties
ProtagonistRole, SceneRole, BFO_0000001, BFO_0000002, BFO_0000017, BFO_0000023
J
Jake — Protagonist
IRI
http://example.com/scene-ontology#jakeProtagonistRole
No annotation properties
AntagonistRole, SceneRole, BFO_0000001, BFO_0000002, BFO_0000017, BFO_0000023
V
Vera — Antagonist
IRI
http://example.com/scene-ontology#veraAntagonistRole
No annotation properties
SceneRole, SupportingRole, BFO_0000001, BFO_0000002, BFO_0000017, BFO_0000023
S
Sam — Supporting
IRI
http://example.com/scene-ontology#samSupportingRole
No annotation properties
\ No newline at end of file diff --git a/docs/mcp-demo/scene-ontology/graph.ttl b/docs/mcp-demo/scene-ontology/graph.ttl index 008ef10f..809d3b42 100644 --- a/docs/mcp-demo/scene-ontology/graph.ttl +++ b/docs/mcp-demo/scene-ontology/graph.ttl @@ -5,11 +5,11 @@ @prefix rdfs: . @prefix xml: . @prefix xsd: . -@prefix : . @prefix spw: . @prefix dcterms: . @prefix p-plan: . @prefix prov: . +@prefix : . @prefix terms: . @prefix vann: . @prefix dtype: . diff --git a/docs/mcp-demo/seeds/foaf-social-network.md b/docs/mcp-demo/seeds/foaf-social-network.md index 52e00469..4c0c63ea 100644 --- a/docs/mcp-demo/seeds/foaf-social-network.md +++ b/docs/mcp-demo/seeds/foaf-social-network.md @@ -21,12 +21,12 @@ every node and axiom will be visible on canvas. `{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://xmlns.com/foaf/0.1/Organization","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"foaf:Organization"}}}` `{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://xmlns.com/foaf/0.1/knows","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"foaf:knows"}}}` `{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://xmlns.com/foaf/0.1/member","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"foaf:member"}}}` -`{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/Person","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://xmlns.com/foaf/0.1/Agent"}}}` -`{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/Organization","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://xmlns.com/foaf/0.1/Agent"}}}` -`{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/knows","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://xmlns.com/foaf/0.1/Person"}}}` -`{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/knows","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://xmlns.com/foaf/0.1/Person"}}}` -`{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/member","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://xmlns.com/foaf/0.1/Agent"}}}` -`{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/member","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://xmlns.com/foaf/0.1/Organization"}}}` +`{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/Person","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://xmlns.com/foaf/0.1/Agent"}}}` +`{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/Organization","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://xmlns.com/foaf/0.1/Agent"}}}` +`{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/knows","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://xmlns.com/foaf/0.1/Person"}}}` +`{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/knows","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://xmlns.com/foaf/0.1/Person"}}}` +`{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/member","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://xmlns.com/foaf/0.1/Agent"}}}` +`{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://xmlns.com/foaf/0.1/member","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://xmlns.com/foaf/0.1/Organization"}}}` ```tool-result @@ -45,17 +45,17 @@ every node and axiom will be visible on canvas. `{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.org/ex#manages","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"manages"}}}` `{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.org/ex#reportsTo","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"reportsTo"}}}` `{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.org/ex#collaboratesWith","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"collaboratesWith"}}}` -`{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#Employee","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://xmlns.com/foaf/0.1/Person"}}}` -`{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#Manager","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.org/ex#Employee"}}}` -`{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#Executive","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.org/ex#Manager"}}}` -`{"jsonrpc":"2.0","id":13,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#Department","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://xmlns.com/foaf/0.1/Organization"}}}` -`{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#manages","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.org/ex#Manager"}}}` -`{"jsonrpc":"2.0","id":15,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#manages","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.org/ex#Employee"}}}` -`{"jsonrpc":"2.0","id":16,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#reportsTo","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.org/ex#Employee"}}}` -`{"jsonrpc":"2.0","id":17,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#reportsTo","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.org/ex#Manager"}}}` -`{"jsonrpc":"2.0","id":18,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#collaboratesWith","predicateIri":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","objectIri":"http://www.w3.org/2002/07/owl#SymmetricProperty"}}}` -`{"jsonrpc":"2.0","id":19,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#collaboratesWith","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.org/ex#Employee"}}}` -`{"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/ex#collaboratesWith","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.org/ex#Employee"}}}` +`{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#Employee","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://xmlns.com/foaf/0.1/Person"}}}` +`{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#Manager","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.org/ex#Employee"}}}` +`{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#Executive","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.org/ex#Manager"}}}` +`{"jsonrpc":"2.0","id":13,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#Department","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://xmlns.com/foaf/0.1/Organization"}}}` +`{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#manages","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.org/ex#Manager"}}}` +`{"jsonrpc":"2.0","id":15,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#manages","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.org/ex#Employee"}}}` +`{"jsonrpc":"2.0","id":16,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#reportsTo","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.org/ex#Employee"}}}` +`{"jsonrpc":"2.0","id":17,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#reportsTo","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.org/ex#Manager"}}}` +`{"jsonrpc":"2.0","id":18,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#collaboratesWith","predicateIri":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","objectIri":"http://www.w3.org/2002/07/owl#SymmetricProperty"}}}` +`{"jsonrpc":"2.0","id":19,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#collaboratesWith","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.org/ex#Employee"}}}` +`{"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/ex#collaboratesWith","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.org/ex#Employee"}}}` `{"jsonrpc":"2.0","id":21,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":200}}}` `{"jsonrpc":"2.0","id":22,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":23,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":200}}}` @@ -98,22 +98,22 @@ until the reasoner fires. **Assistant:** This gives us a mix of formal hierarchy and informal social ties — exactly the kind of graph where reasoning reveals hidden structure. Batching all edges then laying out. -`{"jsonrpc":"2.0","id":33,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/alice","predicateIri":"http://example.org/ex#manages","objectIri":"http://example.org/bob"}}}` -`{"jsonrpc":"2.0","id":34,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/alice","predicateIri":"http://example.org/ex#manages","objectIri":"http://example.org/frank"}}}` -`{"jsonrpc":"2.0","id":35,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/bob","predicateIri":"http://example.org/ex#manages","objectIri":"http://example.org/carol"}}}` -`{"jsonrpc":"2.0","id":36,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/bob","predicateIri":"http://example.org/ex#manages","objectIri":"http://example.org/dave"}}}` -`{"jsonrpc":"2.0","id":37,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/eve","predicateIri":"http://example.org/ex#reportsTo","objectIri":"http://example.org/alice"}}}` -`{"jsonrpc":"2.0","id":38,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/carol","predicateIri":"http://example.org/ex#reportsTo","objectIri":"http://example.org/bob"}}}` -`{"jsonrpc":"2.0","id":39,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/dave","predicateIri":"http://example.org/ex#reportsTo","objectIri":"http://example.org/bob"}}}` -`{"jsonrpc":"2.0","id":40,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/dave","predicateIri":"http://example.org/ex#collaboratesWith","objectIri":"http://example.org/eve"}}}` -`{"jsonrpc":"2.0","id":41,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/alice","predicateIri":"http://xmlns.com/foaf/0.1/knows","objectIri":"http://example.org/eve"}}}` -`{"jsonrpc":"2.0","id":42,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/bob","predicateIri":"http://xmlns.com/foaf/0.1/knows","objectIri":"http://example.org/carol"}}}` -`{"jsonrpc":"2.0","id":43,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/alice","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/engineering"}}}` -`{"jsonrpc":"2.0","id":44,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/bob","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/engineering"}}}` -`{"jsonrpc":"2.0","id":45,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/carol","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/engineering"}}}` -`{"jsonrpc":"2.0","id":46,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/dave","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/research"}}}` -`{"jsonrpc":"2.0","id":47,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/eve","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/research"}}}` -`{"jsonrpc":"2.0","id":48,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.org/frank","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/research"}}}` +`{"jsonrpc":"2.0","id":33,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/alice","predicateIri":"http://example.org/ex#manages","objectIri":"http://example.org/bob"}}}` +`{"jsonrpc":"2.0","id":34,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/alice","predicateIri":"http://example.org/ex#manages","objectIri":"http://example.org/frank"}}}` +`{"jsonrpc":"2.0","id":35,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/bob","predicateIri":"http://example.org/ex#manages","objectIri":"http://example.org/carol"}}}` +`{"jsonrpc":"2.0","id":36,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/bob","predicateIri":"http://example.org/ex#manages","objectIri":"http://example.org/dave"}}}` +`{"jsonrpc":"2.0","id":37,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/eve","predicateIri":"http://example.org/ex#reportsTo","objectIri":"http://example.org/alice"}}}` +`{"jsonrpc":"2.0","id":38,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/carol","predicateIri":"http://example.org/ex#reportsTo","objectIri":"http://example.org/bob"}}}` +`{"jsonrpc":"2.0","id":39,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/dave","predicateIri":"http://example.org/ex#reportsTo","objectIri":"http://example.org/bob"}}}` +`{"jsonrpc":"2.0","id":40,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/dave","predicateIri":"http://example.org/ex#collaboratesWith","objectIri":"http://example.org/eve"}}}` +`{"jsonrpc":"2.0","id":41,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/alice","predicateIri":"http://xmlns.com/foaf/0.1/knows","objectIri":"http://example.org/eve"}}}` +`{"jsonrpc":"2.0","id":42,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/bob","predicateIri":"http://xmlns.com/foaf/0.1/knows","objectIri":"http://example.org/carol"}}}` +`{"jsonrpc":"2.0","id":43,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/alice","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/engineering"}}}` +`{"jsonrpc":"2.0","id":44,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/bob","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/engineering"}}}` +`{"jsonrpc":"2.0","id":45,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/carol","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/engineering"}}}` +`{"jsonrpc":"2.0","id":46,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/dave","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/research"}}}` +`{"jsonrpc":"2.0","id":47,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/eve","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/research"}}}` +`{"jsonrpc":"2.0","id":48,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.org/frank","predicateIri":"http://xmlns.com/foaf/0.1/member","objectIri":"http://example.org/research"}}}` `{"jsonrpc":"2.0","id":49,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":200}}}` `{"jsonrpc":"2.0","id":50,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":51,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":200}}}` diff --git a/docs/mcp-demo/seeds/pizza-tutorial.md b/docs/mcp-demo/seeds/pizza-tutorial.md index 16e4defb..8cfd5424 100644 --- a/docs/mcp-demo/seeds/pizza-tutorial.md +++ b/docs/mcp-demo/seeds/pizza-tutorial.md @@ -39,9 +39,9 @@ Declare them pairwise disjoint. reasoner finds an individual typed as both, it signals an inconsistency. We need three pairs to cover every combination. -`{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` -`{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` -`{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` +`{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` +`{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` +`{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` `{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":200}}}` ```tool-result @@ -63,9 +63,9 @@ also disjoint: no base can be both thin and deep pan. `{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"ThinAndCrispyBase"}}}` `{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#DeepPanBase","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"DeepPanBase"}}}` -`{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` -`{"jsonrpc":"2.0","id":13,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#DeepPanBase","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` -`{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#DeepPanBase"}}}` +`{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` +`{"jsonrpc":"2.0","id":13,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#DeepPanBase","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` +`{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#DeepPanBase"}}}` `{"jsonrpc":"2.0","id":15,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":160}}}` ```tool-result @@ -89,10 +89,10 @@ are also implicitly Pizzas through the chain. `{"jsonrpc":"2.0","id":17,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#Margherita","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"Margherita"}}}` `{"jsonrpc":"2.0","id":18,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#AmericanHot","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"AmericanHot"}}}` `{"jsonrpc":"2.0","id":19,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#FruttiDiMare","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"FruttiDiMare"}}}` -`{"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` -`{"jsonrpc":"2.0","id":21,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#Margherita","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza"}}}` -`{"jsonrpc":"2.0","id":22,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#AmericanHot","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza"}}}` -`{"jsonrpc":"2.0","id":23,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#FruttiDiMare","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza"}}}` +`{"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` +`{"jsonrpc":"2.0","id":21,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#Margherita","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza"}}}` +`{"jsonrpc":"2.0","id":22,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#AmericanHot","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza"}}}` +`{"jsonrpc":"2.0","id":23,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#FruttiDiMare","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza"}}}` `{"jsonrpc":"2.0","id":24,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":160}}}` ```tool-result @@ -116,10 +116,10 @@ PizzaTopping through the chain. `{"jsonrpc":"2.0","id":26,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"MeatTopping"}}}` `{"jsonrpc":"2.0","id":27,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"VegetableTopping"}}}` `{"jsonrpc":"2.0","id":28,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#FishTopping","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"FishTopping"}}}` -`{"jsonrpc":"2.0","id":29,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` -`{"jsonrpc":"2.0","id":30,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` -`{"jsonrpc":"2.0","id":31,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` -`{"jsonrpc":"2.0","id":32,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` +`{"jsonrpc":"2.0","id":29,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` +`{"jsonrpc":"2.0","id":30,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` +`{"jsonrpc":"2.0","id":31,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` +`{"jsonrpc":"2.0","id":32,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` `{"jsonrpc":"2.0","id":33,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":160}}}` ```tool-result @@ -140,12 +140,12 @@ this is a real-world constraint we encode explicitly. With four categories there unique pairs. If the reasoner later finds an individual typed as two disjoint categories, it will flag an inconsistency. -`{"jsonrpc":"2.0","id":34,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping"}}}` -`{"jsonrpc":"2.0","id":35,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` -`{"jsonrpc":"2.0","id":36,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping"}}}` -`{"jsonrpc":"2.0","id":37,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` -`{"jsonrpc":"2.0","id":38,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping"}}}` -`{"jsonrpc":"2.0","id":39,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping"}}}` +`{"jsonrpc":"2.0","id":34,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping"}}}` +`{"jsonrpc":"2.0","id":35,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` +`{"jsonrpc":"2.0","id":36,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping"}}}` +`{"jsonrpc":"2.0","id":37,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` +`{"jsonrpc":"2.0","id":38,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping"}}}` +`{"jsonrpc":"2.0","id":39,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping","predicateIri":"http://www.w3.org/2002/07/owl#disjointWith","objectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping"}}}` `{"jsonrpc":"2.0","id":40,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":160}}}` ```tool-result @@ -172,13 +172,13 @@ all the way up to CheeseTopping and then PizzaTopping — no extra assertions ne `{"jsonrpc":"2.0","id":45,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#OliveTopping","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"OliveTopping"}}}` `{"jsonrpc":"2.0","id":46,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#GarlicTopping","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"GarlicTopping"}}}` `{"jsonrpc":"2.0","id":47,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#AnchoviesTopping","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"AnchoviesTopping"}}}` -`{"jsonrpc":"2.0","id":48,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#MozzarellaTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping"}}}` -`{"jsonrpc":"2.0","id":49,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#ParmesanTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping"}}}` -`{"jsonrpc":"2.0","id":50,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#PeperoniSausageTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping"}}}` -`{"jsonrpc":"2.0","id":51,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#TomatoTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` -`{"jsonrpc":"2.0","id":52,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#OliveTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` -`{"jsonrpc":"2.0","id":53,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#GarlicTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` -`{"jsonrpc":"2.0","id":54,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#AnchoviesTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping"}}}` +`{"jsonrpc":"2.0","id":48,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#MozzarellaTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping"}}}` +`{"jsonrpc":"2.0","id":49,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#ParmesanTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#CheeseTopping"}}}` +`{"jsonrpc":"2.0","id":50,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#PeperoniSausageTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#MeatTopping"}}}` +`{"jsonrpc":"2.0","id":51,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#TomatoTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` +`{"jsonrpc":"2.0","id":52,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#OliveTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` +`{"jsonrpc":"2.0","id":53,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#GarlicTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#VegetableTopping"}}}` +`{"jsonrpc":"2.0","id":54,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#AnchoviesTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#FishTopping"}}}` `{"jsonrpc":"2.0","id":55,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":140}}}` `{"jsonrpc":"2.0","id":56,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":57,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":140}}}` @@ -203,10 +203,10 @@ explicit type assertion. `{"jsonrpc":"2.0","id":58,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"hasTopping"}}}` `{"jsonrpc":"2.0","id":59,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#hasBase","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"hasBase"}}}` -`{"jsonrpc":"2.0","id":60,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` -`{"jsonrpc":"2.0","id":61,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` -`{"jsonrpc":"2.0","id":62,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` -`{"jsonrpc":"2.0","id":63,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` +`{"jsonrpc":"2.0","id":60,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` +`{"jsonrpc":"2.0","id":61,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` +`{"jsonrpc":"2.0","id":62,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` +`{"jsonrpc":"2.0","id":63,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` `{"jsonrpc":"2.0","id":64,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":140}}}` ```tool-result @@ -229,13 +229,13 @@ on the real world. `{"jsonrpc":"2.0","id":65,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#isToppingOf","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"isToppingOf"}}}` `{"jsonrpc":"2.0","id":66,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#isBaseOf","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"isBaseOf"}}}` -`{"jsonrpc":"2.0","id":67,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isToppingOf","predicateIri":"http://www.w3.org/2002/07/owl#inverseOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping"}}}` -`{"jsonrpc":"2.0","id":68,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isBaseOf","predicateIri":"http://www.w3.org/2002/07/owl#inverseOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#hasBase"}}}` -`{"jsonrpc":"2.0","id":69,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isToppingOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` -`{"jsonrpc":"2.0","id":70,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isToppingOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` -`{"jsonrpc":"2.0","id":71,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isBaseOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` -`{"jsonrpc":"2.0","id":72,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isBaseOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` -`{"jsonrpc":"2.0","id":73,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","predicateIri":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","objectIri":"http://www.w3.org/2002/07/owl#FunctionalProperty"}}}` +`{"jsonrpc":"2.0","id":67,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isToppingOf","predicateIri":"http://www.w3.org/2002/07/owl#inverseOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping"}}}` +`{"jsonrpc":"2.0","id":68,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isBaseOf","predicateIri":"http://www.w3.org/2002/07/owl#inverseOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#hasBase"}}}` +`{"jsonrpc":"2.0","id":69,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isToppingOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaTopping"}}}` +`{"jsonrpc":"2.0","id":70,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isToppingOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` +`{"jsonrpc":"2.0","id":71,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isBaseOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://www.pizza-ontology.com/pizza.owl#PizzaBase"}}}` +`{"jsonrpc":"2.0","id":72,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#isBaseOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://www.pizza-ontology.com/pizza.owl#Pizza"}}}` +`{"jsonrpc":"2.0","id":73,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","predicateIri":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","objectIri":"http://www.w3.org/2002/07/owl#FunctionalProperty"}}}` `{"jsonrpc":"2.0","id":74,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":140}}}` `{"jsonrpc":"2.0","id":75,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":76,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":140}}}` @@ -309,7 +309,7 @@ inferred as VegetarianPizza, which is semantically wrong but demonstrates exactl closure axioms exist. `{"jsonrpc":"2.0","id":128,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#VegetarianPizza","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"VegetarianPizza"}}}` -`{"jsonrpc":"2.0","id":129,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#VegetarianPizza","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza"}}}` +`{"jsonrpc":"2.0","id":129,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#VegetarianPizza","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://www.pizza-ontology.com/pizza.owl#NamedPizza"}}}` `{"jsonrpc":"2.0","id":130,"method":"tools/call","params":{"name":"loadRdf","arguments":{"turtle":"@prefix owl: .\n@prefix rdf: .\n@prefix pizza: .\n\npizza:VegetarianPizza owl:equivalentClass [\n rdf:type owl:Restriction ;\n owl:onProperty pizza:hasTopping ;\n owl:someValuesFrom pizza:VegetableTopping\n] ."}}}` `{"jsonrpc":"2.0","id":131,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":140}}}` @@ -357,9 +357,9 @@ the most specific leaf class; the subClassOf chain propagates membership upward `{"jsonrpc":"2.0","id":82,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#mozz1","typeIri":"http://www.pizza-ontology.com/pizza.owl#MozzarellaTopping","label":"mozz"}}}` `{"jsonrpc":"2.0","id":83,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#tom1","typeIri":"http://www.pizza-ontology.com/pizza.owl#TomatoTopping","label":"tomato"}}}` `{"jsonrpc":"2.0","id":84,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#thin1","typeIri":"http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase","label":"thin & crispy"}}}` -`{"jsonrpc":"2.0","id":85,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza1","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#mozz1"}}}` -`{"jsonrpc":"2.0","id":86,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza1","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#tom1"}}}` -`{"jsonrpc":"2.0","id":87,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza1","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","objectIri":"http://www.pizza-ontology.com/pizza.owl#thin1"}}}` +`{"jsonrpc":"2.0","id":85,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza1","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#mozz1"}}}` +`{"jsonrpc":"2.0","id":86,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza1","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#tom1"}}}` +`{"jsonrpc":"2.0","id":87,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza1","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","objectIri":"http://www.pizza-ontology.com/pizza.owl#thin1"}}}` `{"jsonrpc":"2.0","id":88,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":180}}}` `{"jsonrpc":"2.0","id":200,"method":"tools/call","params":{"name":"focusNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#pizza1"}}}` @@ -384,10 +384,10 @@ so no conflict. `{"jsonrpc":"2.0","id":92,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#mozz2","typeIri":"http://www.pizza-ontology.com/pizza.owl#MozzarellaTopping","label":"mozz"}}}` `{"jsonrpc":"2.0","id":93,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#olive1","typeIri":"http://www.pizza-ontology.com/pizza.owl#OliveTopping","label":"olive"}}}` `{"jsonrpc":"2.0","id":94,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#deep1","typeIri":"http://www.pizza-ontology.com/pizza.owl#DeepPanBase","label":"deep pan"}}}` -`{"jsonrpc":"2.0","id":95,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza2","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#pep1"}}}` -`{"jsonrpc":"2.0","id":96,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza2","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#mozz2"}}}` -`{"jsonrpc":"2.0","id":97,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza2","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#olive1"}}}` -`{"jsonrpc":"2.0","id":98,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza2","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","objectIri":"http://www.pizza-ontology.com/pizza.owl#deep1"}}}` +`{"jsonrpc":"2.0","id":95,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza2","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#pep1"}}}` +`{"jsonrpc":"2.0","id":96,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza2","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#mozz2"}}}` +`{"jsonrpc":"2.0","id":97,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza2","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#olive1"}}}` +`{"jsonrpc":"2.0","id":98,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza2","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","objectIri":"http://www.pizza-ontology.com/pizza.owl#deep1"}}}` `{"jsonrpc":"2.0","id":99,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":180}}}` `{"jsonrpc":"2.0","id":201,"method":"tools/call","params":{"name":"focusNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#pizza2"}}}` @@ -412,9 +412,9 @@ topping the reasoner will use for classification. `{"jsonrpc":"2.0","id":100,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#anch1","typeIri":"http://www.pizza-ontology.com/pizza.owl#AnchoviesTopping","label":"anchovies"}}}` `{"jsonrpc":"2.0","id":101,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#garlic1","typeIri":"http://www.pizza-ontology.com/pizza.owl#GarlicTopping","label":"garlic"}}}` `{"jsonrpc":"2.0","id":102,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#thin2","typeIri":"http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase","label":"thin & crispy"}}}` -`{"jsonrpc":"2.0","id":103,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza3","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#anch1"}}}` -`{"jsonrpc":"2.0","id":104,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza3","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#garlic1"}}}` -`{"jsonrpc":"2.0","id":105,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza3","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","objectIri":"http://www.pizza-ontology.com/pizza.owl#thin2"}}}` +`{"jsonrpc":"2.0","id":103,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza3","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#anch1"}}}` +`{"jsonrpc":"2.0","id":104,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza3","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#garlic1"}}}` +`{"jsonrpc":"2.0","id":105,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza3","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","objectIri":"http://www.pizza-ontology.com/pizza.owl#thin2"}}}` `{"jsonrpc":"2.0","id":106,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":107,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":180}}}` @@ -440,9 +440,9 @@ fire and classify it — but we also need to observe what the reasoner says abou `{"jsonrpc":"2.0","id":136,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#olive2","typeIri":"http://www.pizza-ontology.com/pizza.owl#OliveTopping","label":"olive"}}}` `{"jsonrpc":"2.0","id":137,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#tom2","typeIri":"http://www.pizza-ontology.com/pizza.owl#TomatoTopping","label":"tomato"}}}` `{"jsonrpc":"2.0","id":138,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#thin3","typeIri":"http://www.pizza-ontology.com/pizza.owl#ThinAndCrispyBase","label":"thin & crispy"}}}` -`{"jsonrpc":"2.0","id":139,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza4","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#olive2"}}}` -`{"jsonrpc":"2.0","id":140,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza4","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#tom2"}}}` -`{"jsonrpc":"2.0","id":141,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza4","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","objectIri":"http://www.pizza-ontology.com/pizza.owl#thin3"}}}` +`{"jsonrpc":"2.0","id":139,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza4","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#olive2"}}}` +`{"jsonrpc":"2.0","id":140,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza4","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasTopping","objectIri":"http://www.pizza-ontology.com/pizza.owl#tom2"}}}` +`{"jsonrpc":"2.0","id":141,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://www.pizza-ontology.com/pizza.owl#pizza4","predicateIri":"http://www.pizza-ontology.com/pizza.owl#hasBase","objectIri":"http://www.pizza-ontology.com/pizza.owl#thin3"}}}` `{"jsonrpc":"2.0","id":142,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":143,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":180}}}` `{"jsonrpc":"2.0","id":202,"method":"tools/call","params":{"name":"focusNode","arguments":{"iri":"http://www.pizza-ontology.com/pizza.owl#pizza4"}}}` diff --git a/docs/mcp-demo/seeds/reasoning-demo.md b/docs/mcp-demo/seeds/reasoning-demo.md index 1e236256..ea8579d2 100644 --- a/docs/mcp-demo/seeds/reasoning-demo.md +++ b/docs/mcp-demo/seeds/reasoning-demo.md @@ -45,25 +45,25 @@ then axioms. Starting in TBox view so every class and property node lands on the **Assistant:** All nodes exist on canvas — batching all `addLink` calls, then layout: -`{"jsonrpc":"2.0","id":13,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#Employee","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/reasoning-demo#Person"}}}` -`{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#Manager","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/reasoning-demo#Employee"}}}` -`{"jsonrpc":"2.0","id":15,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#Executive","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/reasoning-demo#Manager"}}}` -`{"jsonrpc":"2.0","id":16,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasFriend","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subPropertyOf","objectIri":"http://example.com/reasoning-demo#knows"}}}` -`{"jsonrpc":"2.0","id":17,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#isManagedBy","predicateIri":"http://www.w3.org/2002/07/owl#inverseOf","objectIri":"http://example.com/reasoning-demo#manages"}}}` -`{"jsonrpc":"2.0","id":18,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#isColleagueOf","predicateIri":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","objectIri":"http://www.w3.org/2002/07/owl#SymmetricProperty"}}}` -`{"jsonrpc":"2.0","id":19,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasSupervisor","predicateIri":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","objectIri":"http://www.w3.org/2002/07/owl#TransitiveProperty"}}}` -`{"jsonrpc":"2.0","id":21,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#knows","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Person"}}}` -`{"jsonrpc":"2.0","id":22,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#knows","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Person"}}}` -`{"jsonrpc":"2.0","id":23,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasFriend","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Person"}}}` -`{"jsonrpc":"2.0","id":24,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasFriend","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Person"}}}` -`{"jsonrpc":"2.0","id":25,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#manages","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Manager"}}}` -`{"jsonrpc":"2.0","id":26,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#manages","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Employee"}}}` -`{"jsonrpc":"2.0","id":27,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#isManagedBy","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Employee"}}}` -`{"jsonrpc":"2.0","id":28,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#isManagedBy","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Manager"}}}` -`{"jsonrpc":"2.0","id":29,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#isColleagueOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Employee"}}}` -`{"jsonrpc":"2.0","id":30,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#isColleagueOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Employee"}}}` -`{"jsonrpc":"2.0","id":31,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasSupervisor","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Employee"}}}` -`{"jsonrpc":"2.0","id":32,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasSupervisor","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Manager"}}}` +`{"jsonrpc":"2.0","id":13,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#Employee","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/reasoning-demo#Person"}}}` +`{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#Manager","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/reasoning-demo#Employee"}}}` +`{"jsonrpc":"2.0","id":15,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#Executive","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/reasoning-demo#Manager"}}}` +`{"jsonrpc":"2.0","id":16,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasFriend","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subPropertyOf","objectIri":"http://example.com/reasoning-demo#knows"}}}` +`{"jsonrpc":"2.0","id":17,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#isManagedBy","predicateIri":"http://www.w3.org/2002/07/owl#inverseOf","objectIri":"http://example.com/reasoning-demo#manages"}}}` +`{"jsonrpc":"2.0","id":18,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#isColleagueOf","predicateIri":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","objectIri":"http://www.w3.org/2002/07/owl#SymmetricProperty"}}}` +`{"jsonrpc":"2.0","id":19,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasSupervisor","predicateIri":"http://www.w3.org/1999/02/22-rdf-syntax-ns#type","objectIri":"http://www.w3.org/2002/07/owl#TransitiveProperty"}}}` +`{"jsonrpc":"2.0","id":21,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#knows","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Person"}}}` +`{"jsonrpc":"2.0","id":22,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#knows","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Person"}}}` +`{"jsonrpc":"2.0","id":23,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasFriend","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Person"}}}` +`{"jsonrpc":"2.0","id":24,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasFriend","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Person"}}}` +`{"jsonrpc":"2.0","id":25,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#manages","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Manager"}}}` +`{"jsonrpc":"2.0","id":26,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#manages","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Employee"}}}` +`{"jsonrpc":"2.0","id":27,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#isManagedBy","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Employee"}}}` +`{"jsonrpc":"2.0","id":28,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#isManagedBy","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Manager"}}}` +`{"jsonrpc":"2.0","id":29,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#isColleagueOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Employee"}}}` +`{"jsonrpc":"2.0","id":30,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#isColleagueOf","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Employee"}}}` +`{"jsonrpc":"2.0","id":31,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasSupervisor","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/reasoning-demo#Employee"}}}` +`{"jsonrpc":"2.0","id":32,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#hasSupervisor","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/reasoning-demo#Manager"}}}` `{"jsonrpc":"2.0","id":34,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":200}}}` `{"jsonrpc":"2.0","id":35,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":36,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":200}}}` @@ -103,12 +103,12 @@ Bob, Bob and Carol are colleagues, and the supervisor chain is Carol → Bob → **Assistant:** Six edges, all nodes already on canvas — batching them all, then layout so you can see the ABox shape before we run the reasoner: -`{"jsonrpc":"2.0","id":43,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#alice","predicateIri":"http://example.com/reasoning-demo#manages","objectIri":"http://example.com/reasoning-demo#carol"}}}` -`{"jsonrpc":"2.0","id":44,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#dave","predicateIri":"http://example.com/reasoning-demo#manages","objectIri":"http://example.com/reasoning-demo#bob"}}}` -`{"jsonrpc":"2.0","id":45,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#alice","predicateIri":"http://example.com/reasoning-demo#hasFriend","objectIri":"http://example.com/reasoning-demo#bob"}}}` -`{"jsonrpc":"2.0","id":46,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#bob","predicateIri":"http://example.com/reasoning-demo#isColleagueOf","objectIri":"http://example.com/reasoning-demo#carol"}}}` -`{"jsonrpc":"2.0","id":47,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#carol","predicateIri":"http://example.com/reasoning-demo#hasSupervisor","objectIri":"http://example.com/reasoning-demo#bob"}}}` -`{"jsonrpc":"2.0","id":48,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/reasoning-demo#bob","predicateIri":"http://example.com/reasoning-demo#hasSupervisor","objectIri":"http://example.com/reasoning-demo#alice"}}}` +`{"jsonrpc":"2.0","id":43,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#alice","predicateIri":"http://example.com/reasoning-demo#manages","objectIri":"http://example.com/reasoning-demo#carol"}}}` +`{"jsonrpc":"2.0","id":44,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#dave","predicateIri":"http://example.com/reasoning-demo#manages","objectIri":"http://example.com/reasoning-demo#bob"}}}` +`{"jsonrpc":"2.0","id":45,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#alice","predicateIri":"http://example.com/reasoning-demo#hasFriend","objectIri":"http://example.com/reasoning-demo#bob"}}}` +`{"jsonrpc":"2.0","id":46,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#bob","predicateIri":"http://example.com/reasoning-demo#isColleagueOf","objectIri":"http://example.com/reasoning-demo#carol"}}}` +`{"jsonrpc":"2.0","id":47,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#carol","predicateIri":"http://example.com/reasoning-demo#hasSupervisor","objectIri":"http://example.com/reasoning-demo#bob"}}}` +`{"jsonrpc":"2.0","id":48,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/reasoning-demo#bob","predicateIri":"http://example.com/reasoning-demo#hasSupervisor","objectIri":"http://example.com/reasoning-demo#alice"}}}` `{"jsonrpc":"2.0","id":49,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":200}}}` `{"jsonrpc":"2.0","id":50,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":51,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":200}}}` diff --git a/docs/mcp-demo/seeds/scene-ontology.md b/docs/mcp-demo/seeds/scene-ontology.md index 87767ec0..d98405ff 100644 --- a/docs/mcp-demo/seeds/scene-ontology.md +++ b/docs/mcp-demo/seeds/scene-ontology.md @@ -27,21 +27,21 @@ node then wiring the subclass chain and property axioms one by one. No bulk load `{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://purl.obolibrary.org/obo/RO_0000057","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"has participant"}}}` `{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://purl.obolibrary.org/obo/RO_0000066","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"occurs in"}}}` `{"jsonrpc":"2.0","id":13,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://purl.obolibrary.org/obo/RO_0000087","typeIri":"http://www.w3.org/2002/07/owl#ObjectProperty","label":"has role"}}}` -`{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000002","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000001"}}}` -`{"jsonrpc":"2.0","id":15,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000003","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000001"}}}` -`{"jsonrpc":"2.0","id":16,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000004","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000002"}}}` -`{"jsonrpc":"2.0","id":17,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000015","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000003"}}}` -`{"jsonrpc":"2.0","id":18,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000017","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000002"}}}` -`{"jsonrpc":"2.0","id":19,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000023","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000017"}}}` -`{"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000029","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000004"}}}` -`{"jsonrpc":"2.0","id":21,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000055","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://purl.obolibrary.org/obo/BFO_0000015"}}}` -`{"jsonrpc":"2.0","id":22,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000055","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://purl.obolibrary.org/obo/BFO_0000017"}}}` -`{"jsonrpc":"2.0","id":23,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000057","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://purl.obolibrary.org/obo/BFO_0000015"}}}` -`{"jsonrpc":"2.0","id":24,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000057","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://purl.obolibrary.org/obo/BFO_0000004"}}}` -`{"jsonrpc":"2.0","id":25,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000066","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://purl.obolibrary.org/obo/BFO_0000015"}}}` -`{"jsonrpc":"2.0","id":26,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000066","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://purl.obolibrary.org/obo/BFO_0000029"}}}` -`{"jsonrpc":"2.0","id":27,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000087","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://purl.obolibrary.org/obo/BFO_0000004"}}}` -`{"jsonrpc":"2.0","id":28,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000087","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://purl.obolibrary.org/obo/BFO_0000023"}}}` +`{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000002","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000001"}}}` +`{"jsonrpc":"2.0","id":15,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000003","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000001"}}}` +`{"jsonrpc":"2.0","id":16,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000004","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000002"}}}` +`{"jsonrpc":"2.0","id":17,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000015","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000003"}}}` +`{"jsonrpc":"2.0","id":18,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000017","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000002"}}}` +`{"jsonrpc":"2.0","id":19,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000023","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000017"}}}` +`{"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000029","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000004"}}}` +`{"jsonrpc":"2.0","id":21,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000055","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://purl.obolibrary.org/obo/BFO_0000015"}}}` +`{"jsonrpc":"2.0","id":22,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/BFO_0000055","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://purl.obolibrary.org/obo/BFO_0000017"}}}` +`{"jsonrpc":"2.0","id":23,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000057","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://purl.obolibrary.org/obo/BFO_0000015"}}}` +`{"jsonrpc":"2.0","id":24,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000057","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://purl.obolibrary.org/obo/BFO_0000004"}}}` +`{"jsonrpc":"2.0","id":25,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000066","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://purl.obolibrary.org/obo/BFO_0000015"}}}` +`{"jsonrpc":"2.0","id":26,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000066","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://purl.obolibrary.org/obo/BFO_0000029"}}}` +`{"jsonrpc":"2.0","id":27,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000087","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://purl.obolibrary.org/obo/BFO_0000004"}}}` +`{"jsonrpc":"2.0","id":28,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000087","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://purl.obolibrary.org/obo/BFO_0000023"}}}` ```tool-result @@ -58,11 +58,11 @@ node then wiring the subclass chain and property axioms one by one. No bulk load `{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.com/scene-ontology#DialogueScene","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"DialogueScene"}}}` `{"jsonrpc":"2.0","id":6,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.com/scene-ontology#ChaseScene","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"ChaseScene"}}}` `{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.com/scene-ontology#ConfrontationScene","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"ConfrontationScene"}}}` -`{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#Scene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000015"}}}` -`{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#ActionScene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#Scene"}}}` -`{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#DialogueScene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#Scene"}}}` -`{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#ChaseScene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#ActionScene"}}}` -`{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#ConfrontationScene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#ActionScene"}}}` +`{"jsonrpc":"2.0","id":8,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#Scene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000015"}}}` +`{"jsonrpc":"2.0","id":9,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#ActionScene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#Scene"}}}` +`{"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#DialogueScene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#Scene"}}}` +`{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#ChaseScene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#ActionScene"}}}` +`{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#ConfrontationScene","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#ActionScene"}}}` ```tool-result @@ -78,10 +78,10 @@ node then wiring the subclass chain and property axioms one by one. No bulk load `{"jsonrpc":"2.0","id":14,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.com/scene-ontology#Location","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"Location"}}}` `{"jsonrpc":"2.0","id":15,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.com/scene-ontology#ExteriorLocation","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"ExteriorLocation"}}}` `{"jsonrpc":"2.0","id":16,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.com/scene-ontology#InteriorLocation","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"InteriorLocation"}}}` -`{"jsonrpc":"2.0","id":17,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#Character","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000004"}}}` -`{"jsonrpc":"2.0","id":18,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#Location","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000029"}}}` -`{"jsonrpc":"2.0","id":19,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#ExteriorLocation","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#Location"}}}` -`{"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#InteriorLocation","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#Location"}}}` +`{"jsonrpc":"2.0","id":17,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#Character","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000004"}}}` +`{"jsonrpc":"2.0","id":18,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#Location","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000029"}}}` +`{"jsonrpc":"2.0","id":19,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#ExteriorLocation","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#Location"}}}` +`{"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#InteriorLocation","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#Location"}}}` ```tool-result @@ -97,16 +97,16 @@ node then wiring the subclass chain and property axioms one by one. No bulk load `{"jsonrpc":"2.0","id":22,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.com/scene-ontology#ProtagonistRole","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"ProtagonistRole"}}}` `{"jsonrpc":"2.0","id":23,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.com/scene-ontology#AntagonistRole","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"AntagonistRole"}}}` `{"jsonrpc":"2.0","id":24,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"http://example.com/scene-ontology#SupportingRole","typeIri":"http://www.w3.org/2002/07/owl#Class","label":"SupportingRole"}}}` -`{"jsonrpc":"2.0","id":25,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#SceneRole","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000023"}}}` -`{"jsonrpc":"2.0","id":26,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#ProtagonistRole","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#SceneRole"}}}` -`{"jsonrpc":"2.0","id":27,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#AntagonistRole","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#SceneRole"}}}` -`{"jsonrpc":"2.0","id":28,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#SupportingRole","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#SceneRole"}}}` -`{"jsonrpc":"2.0","id":29,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000087","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/scene-ontology#Character"}}}` -`{"jsonrpc":"2.0","id":30,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000087","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/scene-ontology#SceneRole"}}}` -`{"jsonrpc":"2.0","id":31,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000057","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/scene-ontology#Scene"}}}` -`{"jsonrpc":"2.0","id":32,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000057","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/scene-ontology#Character"}}}` -`{"jsonrpc":"2.0","id":33,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000066","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/scene-ontology#Scene"}}}` -`{"jsonrpc":"2.0","id":34,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000066","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/scene-ontology#Location"}}}` +`{"jsonrpc":"2.0","id":25,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#SceneRole","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://purl.obolibrary.org/obo/BFO_0000023"}}}` +`{"jsonrpc":"2.0","id":26,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#ProtagonistRole","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#SceneRole"}}}` +`{"jsonrpc":"2.0","id":27,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#AntagonistRole","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#SceneRole"}}}` +`{"jsonrpc":"2.0","id":28,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#SupportingRole","predicateIri":"http://www.w3.org/2000/01/rdf-schema#subClassOf","objectIri":"http://example.com/scene-ontology#SceneRole"}}}` +`{"jsonrpc":"2.0","id":29,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000087","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/scene-ontology#Character"}}}` +`{"jsonrpc":"2.0","id":30,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000087","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/scene-ontology#SceneRole"}}}` +`{"jsonrpc":"2.0","id":31,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000057","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/scene-ontology#Scene"}}}` +`{"jsonrpc":"2.0","id":32,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000057","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/scene-ontology#Character"}}}` +`{"jsonrpc":"2.0","id":33,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000066","predicateIri":"http://www.w3.org/2000/01/rdf-schema#domain","objectIri":"http://example.com/scene-ontology#Scene"}}}` +`{"jsonrpc":"2.0","id":34,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://purl.obolibrary.org/obo/RO_0000066","predicateIri":"http://www.w3.org/2000/01/rdf-schema#range","objectIri":"http://example.com/scene-ontology#Location"}}}` `{"jsonrpc":"2.0","id":35,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":200}}}` `{"jsonrpc":"2.0","id":36,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":37,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"elk-layered","spacing":200}}}` @@ -150,18 +150,18 @@ from the `has_role` relationships we assert next, since `RO:has_role rdfs:domain **Assistant:** Wiring the ABox: scene participation, location, and role-bearing assertions. Then layout and snapshot. -`{"jsonrpc":"2.0","id":49,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/RO_0000057","objectIri":"http://example.com/scene-ontology#jake"}}}` -`{"jsonrpc":"2.0","id":50,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/RO_0000057","objectIri":"http://example.com/scene-ontology#vera"}}}` -`{"jsonrpc":"2.0","id":51,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/RO_0000066","objectIri":"http://example.com/scene-ontology#rooftop"}}}` -`{"jsonrpc":"2.0","id":52,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#cafeDialogue","predicateIri":"http://purl.obolibrary.org/obo/RO_0000057","objectIri":"http://example.com/scene-ontology#jake"}}}` -`{"jsonrpc":"2.0","id":53,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#cafeDialogue","predicateIri":"http://purl.obolibrary.org/obo/RO_0000057","objectIri":"http://example.com/scene-ontology#sam"}}}` -`{"jsonrpc":"2.0","id":54,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#cafeDialogue","predicateIri":"http://purl.obolibrary.org/obo/RO_0000066","objectIri":"http://example.com/scene-ontology#cafe"}}}` -`{"jsonrpc":"2.0","id":55,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#jake","predicateIri":"http://purl.obolibrary.org/obo/RO_0000087","objectIri":"http://example.com/scene-ontology#jakeProtagonistRole"}}}` -`{"jsonrpc":"2.0","id":56,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#vera","predicateIri":"http://purl.obolibrary.org/obo/RO_0000087","objectIri":"http://example.com/scene-ontology#veraAntagonistRole"}}}` -`{"jsonrpc":"2.0","id":57,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#sam","predicateIri":"http://purl.obolibrary.org/obo/RO_0000087","objectIri":"http://example.com/scene-ontology#samSupportingRole"}}}` -`{"jsonrpc":"2.0","id":58,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/BFO_0000055","objectIri":"http://example.com/scene-ontology#jakeProtagonistRole"}}}` -`{"jsonrpc":"2.0","id":59,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/BFO_0000055","objectIri":"http://example.com/scene-ontology#veraAntagonistRole"}}}` -`{"jsonrpc":"2.0","id":60,"method":"tools/call","params":{"name":"addLink","arguments":{"subjectIri":"http://example.com/scene-ontology#cafeDialogue","predicateIri":"http://purl.obolibrary.org/obo/BFO_0000055","objectIri":"http://example.com/scene-ontology#samSupportingRole"}}}` +`{"jsonrpc":"2.0","id":49,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/RO_0000057","objectIri":"http://example.com/scene-ontology#jake"}}}` +`{"jsonrpc":"2.0","id":50,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/RO_0000057","objectIri":"http://example.com/scene-ontology#vera"}}}` +`{"jsonrpc":"2.0","id":51,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/RO_0000066","objectIri":"http://example.com/scene-ontology#rooftop"}}}` +`{"jsonrpc":"2.0","id":52,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#cafeDialogue","predicateIri":"http://purl.obolibrary.org/obo/RO_0000057","objectIri":"http://example.com/scene-ontology#jake"}}}` +`{"jsonrpc":"2.0","id":53,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#cafeDialogue","predicateIri":"http://purl.obolibrary.org/obo/RO_0000057","objectIri":"http://example.com/scene-ontology#sam"}}}` +`{"jsonrpc":"2.0","id":54,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#cafeDialogue","predicateIri":"http://purl.obolibrary.org/obo/RO_0000066","objectIri":"http://example.com/scene-ontology#cafe"}}}` +`{"jsonrpc":"2.0","id":55,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#jake","predicateIri":"http://purl.obolibrary.org/obo/RO_0000087","objectIri":"http://example.com/scene-ontology#jakeProtagonistRole"}}}` +`{"jsonrpc":"2.0","id":56,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#vera","predicateIri":"http://purl.obolibrary.org/obo/RO_0000087","objectIri":"http://example.com/scene-ontology#veraAntagonistRole"}}}` +`{"jsonrpc":"2.0","id":57,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#sam","predicateIri":"http://purl.obolibrary.org/obo/RO_0000087","objectIri":"http://example.com/scene-ontology#samSupportingRole"}}}` +`{"jsonrpc":"2.0","id":58,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/BFO_0000055","objectIri":"http://example.com/scene-ontology#jakeProtagonistRole"}}}` +`{"jsonrpc":"2.0","id":59,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#rooftopChase","predicateIri":"http://purl.obolibrary.org/obo/BFO_0000055","objectIri":"http://example.com/scene-ontology#veraAntagonistRole"}}}` +`{"jsonrpc":"2.0","id":60,"method":"tools/call","params":{"name":"addTriple","arguments":{"subjectIri":"http://example.com/scene-ontology#cafeDialogue","predicateIri":"http://purl.obolibrary.org/obo/BFO_0000055","objectIri":"http://example.com/scene-ontology#samSupportingRole"}}}` `{"jsonrpc":"2.0","id":61,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":200}}}` `{"jsonrpc":"2.0","id":62,"method":"tools/call","params":{"name":"expandNode","arguments":{}}}` `{"jsonrpc":"2.0","id":63,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr","spacing":200}}}` diff --git a/docs/owui-relay-session.md b/docs/owui-relay-session.md new file mode 100644 index 00000000..760f1474 --- /dev/null +++ b/docs/owui-relay-session.md @@ -0,0 +1,296 @@ +# OWUI Relay Session Guide + +How to run an interactive OpenWebUI ↔ Ontosphere relay session using the Playwright MCP browser. + +--- + +## What the relay is + +The bookmarklet (`public/relay-bookmarklet.js`) bridges an OpenWebUI chat tab and the Ontosphere app tab inside the **same browser process**. It works via `BroadcastChannel`: + +``` +OWUI chat output + → MutationObserver watches for backtick-wrapped JSON-RPC + → relay parses & forwards via BroadcastChannel("vg-relay") + → Ontosphere tab receives, executes MCP tool, returns result + → relay injects [Ontosphere — N tools ✓] back into OWUI input + → doSubmit fires, model sees the result +``` + +Key constraint: **BroadcastChannel is scoped to a single browser origin and process.** Everything must happen inside the same MCP browser instance. No Node.js scripts outside it. + +--- + +## Prerequisites + +1. **Ontosphere running** at `http://docker-dev.iwm.fraunhofer.de:8080` (use docker-dev, not localhost — relay popup needs same origin for BroadcastChannel to work across tabs). + +2. **Auth cookie** saved at `.playwright/owui-auth.json`. Refresh if expired: + ```bash + OWUI_URL=https://gpuserver1-sit.iwm.fraunhofer.de \ + OWUI_EMAIL=agent@example.de \ + OWUI_PASSWORD=peter123 \ + npm run demo:owui:auth + ``` + +3. **MCP browser** open (Playwright MCP tools active in session). + +--- + +## Startup sequence — two-tab (proven, interactive sessions) + +**This is the primary approach.** OWUI and Ontosphere each run in their own tab — no iframes, no route interception, no streaming issues. + +### Tab 0 — Ontosphere + +```text +browser_navigate → http://docker-dev.iwm.fraunhofer.de:8080 +browser_wait_for → window.__mcpTools?.addNode +``` + +### Tab 1 — OpenWebUI + +Open new tab, navigate to OWUI, inject auth cookie (httpOnly — must use `context.addCookies`, not `document.cookie`): + +```js +// see .playwright/owui-auth-inject.js +const fs = require('fs'); +const auth = JSON.parse(fs.readFileSync('.playwright/owui-auth.json', 'utf8')); +await page.context().addCookies(auth.cookies); +await page.goto('https://gpuserver1-sit.iwm.fraunhofer.de'); +``` + +### Run fresh-setup.js (on Tab 1) + +Selects `qwen3:4b` and clears any prior input. **Note:** script hardcodes `qwen3:4b-instruct` — verify model is selected before continuing, or select manually via the dropdown. + +### Run send-starter.js (on Tab 1) + +Does everything in one shot: +1. Types plain-text seed → Enter → navigates to `/c/` (no backticks = no Notes routing) +2. Fetches `relay-bookmarklet.js` from Ontosphere tab, patches URLs, exposes `__vgInjectResult` / `__vgIsStreaming` / `__vgWaitForIdle` +3. Polls `__vgIsStreaming()` until false (relay-only idle detection — no `waitForSelector`) +4. Injects relay format reminder via `__vgInjectResult` +5. Fallback-clicks `#send-message-button` if relay auto-submit didn't fire + +### Wait for INSTR response, then run send-pizza.js (on Tab 1) + +Poll `__vgIsStreaming()` until false, then run send-pizza.js to inject the pizza task. + +**Idle poll pattern (relay-only):** +```js +const deadline = Date.now() + 600_000; +while (Date.now() < deadline) { + if (!await page.evaluate(() => window.__vgIsStreaming())) break; + await page.waitForTimeout(1000); +} +``` + +--- + +## Demo recording approach — iframe stage (pizza-demo-setup.js) + +**Use this only for screen-recorded demos.** Loads OWUI and Ontosphere side-by-side in iframes at 1920×1080. Has known constraints vs. the two-tab approach: + +- Requires `context.route()` to strip `X-Frame-Options` from OWUI HTML responses +- **Route handler must skip `/api/` and `/socket.io`** — buffering SSE breaks LLM streaming +- No `/no_think` prefix on the seed — causes qwen3 to output `{}` (empty JSON error) +- Requires auth cookie injection at context level before navigation + +Run as inline `code=` in `browser_run_code_unsafe` (vm sandbox has no `require`/`fs`): +1. Read token: `.playwright/owui-auth.json → cookies[0].value` +2. Paste token value over `PASTE_TOKEN_HERE` in the script +3. Paste full script as `code=` into `browser_run_code_unsafe` + +After `{ ok: true }`: recording view active, relay connected, Turn 1 dispatched. + +### Side-by-side stage URL + +```text +http://docker-dev.iwm.fraunhofer.de:8080/demo-stage-owui.html + ?owui=https://gpuserver1-sit.iwm.fraunhofer.de/ + &app=http://docker-dev.iwm.fraunhofer.de:8080/ +``` + +--- + +## Manual startup sequence (without the setup scripts) + +### Step 1: Auth + route setup + +In `browser_run_code_unsafe`: +```js +const fs = require('fs'); +const auth = JSON.parse(fs.readFileSync('/home/hanke/ontosphere/.playwright/owui-auth.json', 'utf8')); +const tok = auth.cookies.find(c => c.name === 'token'); +await page.context().addCookies([{ ...tok, domain: 'gpuserver1-sit.iwm.fraunhofer.de' }]); +await page.context().route('https://gpuserver1-sit.iwm.fraunhofer.de/**', async route => { + try { + const r = await route.fetch(); + const h = { ...r.headers() }; + delete h['x-frame-options']; delete h['content-security-policy']; + await route.fulfill({ response: r, headers: h }); + } catch { await route.continue(); } +}); +``` + +### Step 2: Navigate to stage, wait for frames + +```js +await page.goto('http://docker-dev.iwm.fraunhofer.de:8080/demo-stage-owui.html?owui=...&app=...'); +await page.setViewportSize({ width: 1920, height: 1080 }); +// Find frames: +const appFrame = page.frames().find(f => f.url().startsWith('http://docker-dev') && !f.url().includes('demo-stage')); +const owuiFrame = page.frames().find(f => f.url().startsWith('https://gpuserver1-sit')); +``` + +### Step 3: Inject relay + +```js +const relayCode = await appFrame.evaluate(async () => { + let src = await (await fetch('/relay-bookmarklet.js')).text(); + src = src.replace(/__RELAY_URL__/g, 'http://docker-dev.iwm.fraunhofer.de:8080/relay.html'); + src = src.replace(/__RELAY_ORIGIN__/g, 'http://docker-dev.iwm.fraunhofer.de:8080'); + src = src.replace(/\}\)\(\);\s*$/, ' window.__vgInjectResult = injectResult;\n window.__vgIsStreaming = isAiStreaming;\n})();'); + return src; +}); +await owuiFrame.addScriptTag({ content: relayCode }); +``` + +### Step 4: Inject a turn + +```js +await owuiFrame.evaluate((text) => window.__vgInjectResult(text), 'your turn text here'); +// Fallback if auto-submit didn't fire: +const btn = await owuiFrame.$('#send-message-button:not([disabled])'); +if (btn) await btn.click(); +``` + +--- + +## Relay globals after inject + +After injection, these are available on the OWUI frame: + +| Global | Description | +|--------|-------------| +| `window.__vgInjectResult(text)` | Set TipTap content + auto-submit | +| `window.__vgIsStreaming()` | `true` if model is still generating | +| `window.__vgWaitForIdle(container, cb)` | Poll until DOM stable + not streaming | + +--- + +## Driving turns: turn-driver.js + +After setup, each subsequent turn uses `.playwright/turn-driver.js` as a template (paste inline, fill `CAPTION` and `NEXT_TURN`): + +```js +// Key pattern — substitute CAPTION and NEXT_TURN per turn: +const chatFL = page.frameLocator('#chat-frame'); +const appFrame = () => page.frames().find(f => + f.url().startsWith('http://docker-dev.iwm.fraunhofer.de:8080') && !f.url().includes('demo-stage')); + +// 1. Wait for model done +await chatFL.locator('button[aria-label="Good Response"]').last() + .waitFor({ state: 'visible', timeout: 180000 }); + +// 2. Set caption on Ontosphere canvas +await appFrame()?.evaluate((text) => { + let el = document.getElementById('__demo_caption'); + if (!el) { el = document.createElement('div'); el.id = '__demo_caption'; /* style... */ document.body.appendChild(el); } + el.textContent = text; +}, 'Your caption here'); + +// 3. Inject next turn via relay globals +await chatFL.evaluate((text) => window.__vgInjectResult(text), 'Next turn text'); +await page.waitForTimeout(800); +await chatFL.locator('#send-message-button:not([disabled])').click().catch(() => {}); +``` + +Note: use `chatFL.evaluate()` (FrameLocator evaluate), **not** `owuiFrame.evaluate()` — cross-origin iframes need frameLocator to reach the correct JS context. + +--- + +## Format enforcement + +**All non-JSON-RPC formats are silently ignored.** No error, no response, nothing. This affects: +- OpenAI `function_call` / `tool_calls` +- Claude `tool_use` blocks +- Gemini `functionCall` +- `{"tool":"x","input":{}}` style +- `` XML tags +- Triple-backtick JSON blocks +- Plain prose describing a tool call + +The INSTR injected by `pizza-demo-setup.js` states this explicitly. If the model uses its native tool format, inject a correction: + +``` +The relay only intercepts JSON-RPC 2.0 in single backticks. Your last response used [native format] which was silently ignored. Please retry using: +`{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"...","arguments":{...}}}` +``` + +--- + +## Known model quirks + +### qwen3 thinking phase + +qwen3 emits a long `...` block before its response. The relay's `isAiStreaming()` uses spinner/stop-button detection — it correctly waits for the full response including thinking to finish before checking. Do not inject during the thinking phase. + +### Format reversion + +After receiving the INSTR, most models switch to JSON-RPC. If they revert to native format, re-inject the INSTR via `__vgInjectResult`. + +--- + +## Automated Playwright recording (alternative) + +For headless automated recording without MCP, use the existing spec and config: + +```bash +# Requires OWUI to be accessible and auth cookie saved +OWUI_URL=https://gpuserver1-sit.iwm.fraunhofer.de \ +npm run demo:owui:video +# or directly: +xvfb-run --auto-servernum --server-args='-screen 0 1920x1080x24' \ + npx playwright test e2e/demo-openwebui-socratic.spec.ts \ + --config=playwright.openwebui.config.ts +``` + +This runs headless (via xvfb) with Playwright's built-in video recording (`video: mode: 'on'` in `playwright.openwebui.config.ts`). Output: `test-results/demo-openwebui/`. + +The `pizza-demo-setup.js` approach is for **interactive MCP sessions** (live relay, manual turn injection). The Playwright spec is for **automated recordings** of fixed scenarios. + +--- + +## Debug checklist + +| Symptom | Fix | +|---------|-----| +| `{ ok: false, error: 'OWUI frame not found' }` | X-Frame-Options not stripped — check `context.route()` was added before navigation | +| Content goes to `/notes/` instead of `/c/` | `fill()` used instead of `pressSequentially` + `Enter` | +| `__vgInjectResult` undefined | `addScriptTag` failed or relay code fetch returned 404 — check Ontosphere is running | +| Content injected but not submitted | Fallback `sendBtn.click()` should handle this; verify button selector | +| Model outputs native tool call, no `[Ontosphere` result | Silent-ignore; inject correction message | +| Injection during think phase, model echoes UUID | `isAiStreaming()` stop-button check prevents this; verify relay waited for idle | +| Canvas empty after addNode succeeds | addNode adds OWL classes to TBox — switch to TBox view: `__mcpTools.setViewMode({mode:'tbox'})` | +| Same tool calls silently skipped on re-inject | `dispatchedSigs` deduplication — start a new chat to reset (fresh page = fresh relay instance) | +| INSTR example fires as real tool call | `RIGHT:` example contained live backtick JSON-RPC — use `TOOL_NAME`/`N` placeholders, never real IRIs | +| BroadcastChannel not reaching Ontosphere | Wrong origin — stage `app=` param must use docker-dev URL (not localhost) | +| Relay popup blocked | Stage HTML sets `allow-popups-to-escape-sandbox` on OWUI iframe — check it's present | +| MCP browser locked "already in use" | `ps aux \| grep mcp-chrome` → `kill ` | + +--- + +## Playwright scripts reference + +| Script | Purpose | +|--------|---------| +| `.playwright/pizza-demo-setup.js` | **One-shot setup**: stage nav, auth, model, seed, relay inject, Turn 1. Run as inline code (no `filename=` — vm sandbox has no fs access). | +| `.playwright/turn-driver.js` | Template for each subsequent turn: wait idle → caption → inject next | +| `.playwright/inject-relay.js` | Relay-only injection (no seed, no model setup) | +| `.playwright/fresh-setup.js` | Select model, clear input (OWUI tab must already be open) | +| `.playwright/send-starter.js` | Seed + relay inject for tab-based (non-stage) sessions | +| `.playwright/send-task.js` | Generic task injection wrapper | +| `.playwright/send-pizza.js` | Pizza ontology task (thin wrapper over send-task pattern) | +| `.playwright/owui-auth-inject.js` | Inject saved auth cookie into context | diff --git a/docs/solutions/architecture-patterns/owui-relay-fire-on-idle-dispatch-2026-04-30.md b/docs/solutions/architecture-patterns/owui-relay-fire-on-idle-dispatch-2026-04-30.md new file mode 100644 index 00000000..5023a734 --- /dev/null +++ b/docs/solutions/architecture-patterns/owui-relay-fire-on-idle-dispatch-2026-04-30.md @@ -0,0 +1,129 @@ +--- +title: OWUI Relay — Fire-on-Idle Dispatch Replaces MutationObserver +date: 2026-04-30 +category: docs/solutions/architecture-patterns/ +module: owui-relay +problem_type: architecture_pattern +component: tooling +severity: high +applies_when: + - Building or extending the OWUI relay bookmarklet + - AI chat UI has thinking models (qwen3, o1, deepseek-r1) with long generation gaps + - Relay must extract tool calls from complete AI responses, not mid-stream fragments +tags: [owui-relay, fire-on-idle, mutationobserver, thinking-models, dispatch, streaming-detection] +--- + +# OWUI Relay — Fire-on-Idle Dispatch Replaces MutationObserver + +## Context + +The OWUI relay bookmarklet bridges Open WebUI ↔ Ontosphere via BroadcastChannel. It intercepts +single-backtick JSON-RPC 2.0 tool calls from AI responses and dispatches them to Ontosphere. + +The original architecture used a `MutationObserver` that fired on every DOM mutation. This worked +for basic models but broke catastrophically with thinking models (qwen3, deepseek-r1): the observer +fired mid-`` block, injected tool results before the complete response was visible, and +corrupted conversation state — responses echoed OWUI message UUIDs, tool call sequences were +interrupted, and context was lost. + +## Guidance + +Replace the MutationObserver dispatch trigger with a **fire-on-idle poll**: wait until +`isAiStreaming()` returns false (model fully stopped), then read the complete page text, extract +all tool calls at once (deduplicating via `dispatchedSigs`), and execute them in order. + +```js +/* ── Fire-on-idle poll ─────────────────────────────────────────── */ +var lastIdleText = ''; +var idlePollTimer = null; + +function idlePoll() { + if (window.__vgRelayInstanceId !== instanceId) return; // stale instance + if (!isAiStreaming() && !isProcessing && callQueue.length === 0) { + var text = document.body.innerText || document.body.textContent || ''; + if (text !== lastIdleText) { + lastIdleText = text; + var calls = extractAllToolCalls(text, dispatchedSigs); + if (calls.length > 0) { + callQueue = callQueue.concat(calls); + processNextInQueue(); + } + } + } + idlePollTimer = setTimeout(idlePoll, 500); +} + +// Pre-seed dispatchedSigs so INSTR examples on the page don't re-fire +extractAllToolCalls(document.body.innerText || '', dispatchedSigs); +lastIdleText = document.body.innerText || ''; + +idlePoll(); +window.__vgRelayObserver = { disconnect: function () { + clearTimeout(idlePollTimer); idlePollTimer = null; +} }; +``` + +`isAiStreaming()` uses a 4-signal approach (no chat-UI-specific selectors): +1. `aria-disabled`/`aria-busy` on input ancestors (textarea UIs) +2. Visible spinner/animation elements (class name heuristics) +3. Visible stop/abort button with matching text or aria-label +4. DOM mutation rate fallback: `STREAM_QUIET_MS = 4000ms` of quiet = done + +## Why This Matters + +MutationObserver fires on every DOM mutation — including every streamed token. For thinking +models, there can be thousands of mutations during a `` block. Dispatching mid-think: +- Injects tool results before the model has finished reasoning +- Can cause the model to echo the injected OWUI message UUID in its output +- Results in tool calls never being extracted from the actual (post-think) response + +Fire-on-idle reads the COMPLETE response exactly once per turn. Combined with `dispatchedSigs` +deduplication, this is idempotent and safe across multi-turn conversations. + +## When to Apply + +- Any relay targeting an AI chat UI that might use thinking/reasoning models +- Whenever the relay needs to extract structured output (tool calls) from complete AI responses +- When the relay must handle multi-turn conversations without re-dispatching previous calls + +## Examples + +**Before (broken for thinking models):** +``` +DOM mutation → MutationObserver fires → partial text parsed → tool called mid-think +→ model still generating → injected result corrupts context → UUID echo in next response +``` + +**After (fire-on-idle):** +``` +Model generates (think + response) → DOM quiet 4s → isAiStreaming()=false → +idlePoll() reads complete page text → extractAllToolCalls deduplicates → +all new calls queued → executed in order → results injected → model confirms +``` + +**Testing protocol (OWUI + Playwright MCP):** + +1. Send plain-text SEED (no backticks) → wait for `/c/` URL (avoids Notes routing) +2. Inject relay bookmarklet from Ontosphere tab (`vgPage.evaluate` + `addScriptTag`) +3. Wait for `button[aria-label="Good Response"]` — appears only after COMPLETE response + (not `__vgIsStreaming()` which gives false negatives during thinking gaps) +4. Inject INSTR+task — compact format (no `help()` call), WRONG/RIGHT examples inline +5. Use `mistral-small3.1` for test runs (no thinking blocks → faster iteration) + +**INSTR format that works:** +``` +RELAY FORMAT — single backtick only. ALL other formats silently ignored: +`{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"TOOL","arguments":{...}}}` +WRONG: ```json{...}``` or {"tool":"x","params":{}} or {"method":"addNode",...} +RIGHT: `{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"ex:Foo","typeIri":"owl:Class"}}}` +``` + +The INSTR must embed the task inline (no `help()` call) — `help()` returns ~5000 chars which +hits OWUI's output token limit and triggers "Continue Response" truncation. + +## Related + +- `public/relay-bookmarklet.js` — the implementation +- `.playwright/send-starter.js` — the test harness (seed → relay inject → INSTR) +- `.playwright/fresh-setup.js` — model selection (default: qwen3:8b; switch to mistral-small3.1 for testing) +- Auto memory: `project_owui_relay_fire_on_idle.md`, `feedback_owui_session_flow.md` diff --git a/docs/solutions/developer-experience/owui-websocket-blocked-playwright.md b/docs/solutions/developer-experience/owui-websocket-blocked-playwright.md new file mode 100644 index 00000000..fb01fc33 --- /dev/null +++ b/docs/solutions/developer-experience/owui-websocket-blocked-playwright.md @@ -0,0 +1,74 @@ +--- +module: e2e/demo +tags: [playwright, owui, websocket, socket.io, chrome, private-network-access] +problem_type: infrastructure +--- + +# OWUI Socket.io WebSocket Blocked in Playwright Browser + +## Problem + +When Playwright (Chromium) navigates to the OWUI instance at +`https://gpuserver1-sit.iwm.fraunhofer.de/`, the socket.io WebSocket connection +fails with: + +``` +WebSocket connection to 'wss://gpuserver1-sit.iwm.fraunhofer.de/ws/socket.io/...' +failed: Error in connection establishment: net::ERR_BLOCKED_BY_LOCAL_NETWORK_ACCESS_CHECKS +``` + +**Symptom:** OWUI shows the "thinking" black dot indefinitely and never renders the +AI response in the browser window, even though qwen3 responds correctly on the +server (the response is stored in OWUI's database and visible if you reload or +navigate to the chat). + +**Root cause:** Chrome 104+ applies Private Network Access checks. The GPU server +hostname resolves to an internal (Fraunhofer intranet) IP address. Chrome +classifies this as a private network and blocks WebSocket upgrade requests from +an HTTPS page to that endpoint, because the server does not respond to the +`Access-Control-Request-Private-Network` preflight with +`Access-Control-Allow-Private-Network: true`. + +OWUI uses socket.io as its real-time channel to stream tokens into the UI; without +it, the UI does not update until the response is fully loaded via polling. + +## Fix + +Add `--disable-features=BlockInsecurePrivateNetworkRequests` to the Chromium +launch args. This disables Chrome's private network request blocking for the +browser session. + +### `playwright.demo.config.ts` (demo recordings) + +```ts +launchOptions: { + args: ['--disable-features=BlockInsecurePrivateNetworkRequests'], +}, +``` + +### MCP Playwright server (interactive sessions via Claude Code) + +The MCP server is started by the IDE extension as: +``` +npm exec @playwright/mcp@latest --browser chromium --ignore-https-errors +``` + +To add the flag, configure the MCP server launch command in the IDE/settings +to pass it via the `--args` option (if supported by the MCP version) or +replace the launch with: +``` +npx @playwright/mcp --browser chromium --ignore-https-errors \ + --cdp-endpoint http://localhost:9222 +``` +and pre-launch Chrome with: +``` +chromium-browser --remote-debugging-port=9222 \ + --disable-features=BlockInsecurePrivateNetworkRequests +``` + +## Relay impact + +The OWUI relay (bookmarklet/stage page) reads the OWUI DOM via +`MutationObserver`/idle-poll and does **not** depend on socket.io for its +tool-call extraction. The relay still works correctly without socket.io; only +the visual streaming in the Playwright browser is affected. diff --git a/e2e/demo-openwebui-socratic.spec.ts b/e2e/demo-openwebui-socratic.spec.ts new file mode 100644 index 00000000..a97d000f --- /dev/null +++ b/e2e/demo-openwebui-socratic.spec.ts @@ -0,0 +1,518 @@ +/** + * Socratic pizza ontology demo — live qwen3:4b via OWUI relay. + * + * Records a side-by-side stage: OWUI live chat (left iframe) + Ontosphere canvas + * (right iframe). qwen3:4b is guided through a pizza ontology via purely + * conceptual Socratic questions — TBox, ABox, OWL-RL reasoning (T0–T9). + * + * Auth note: context.addInitScript does not run inside iframes. Fix: a throwaway + * authPage pre-seeds OWUI localStorage in the browser context before the stage + * loads; the OWUI iframe then reads pre-existing storage on its own. + * + * Prerequisites: + * 1. npm run dev # Ontosphere at localhost:8080 + * 2. npm run demo:owui:auth # save .playwright/owui-auth.json + * + * Run (records video): + * OWUI_URL=https://gpuserver1-sit.iwm.fraunhofer.de \ + * npm run demo:owui:video + * + * Output: docs/demo-videos/openwebui-socratic.webm / .mp4 + */ + +import { test, Page, Frame } from '@playwright/test'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const OWUI_URL = process.env.OWUI_URL || 'https://gpuserver1-sit.iwm.fraunhofer.de'; +const VG_URL = process.env.DEMO_BASE_URL || 'http://localhost:8080'; +const AUTH_FILE = path.resolve(__dirname, '../.playwright/owui-auth.json'); +const MODEL = 'qwen3:4b'; + +// Caption shown briefly before injection — tells viewer what's being asked +const TURN_TOPICS = [ + 'Asking: what is the most fundamental OWL building block for a concept?', + 'Guide: build full ingredient hierarchy — PizzaTopping, PizzaBase, and 7 leaf varieties.', + 'Guide: model hasPart as an ObjectProperty with rdfs:domain and rdfs:range.', + 'Guide: add named pizza classes — SalamiPizza, HawaiianPizza, MargheritaPizza.', + 'Guide: define equivalentClass axioms via blank-node restrictions — anonymous someValuesFrom.', + 'Guide: expand all TBox nodes to reveal their asserted properties.', + 'Guide: switch to ABox — create 3 untyped pizza individuals.', + 'Guide: build pizza1 — add Salami, Mozzarella, ThinCrust parts and hasPart links.', + 'Guide: build pizza2 (Hawaiian) and pizza3 (Margherita) — add parts and links.', + 'Guide: apply OWL-RL reasoning to derive all inferred triples.', + 'Guide: inspect pizza1, pizza2, pizza3 — trace the classification inference chain.', +]; + +// Caption shown after idle — describes what was just built +const AFTER_CAPTIONS = [ + 'Pizza class on canvas — owl:Class, the atomic unit of OWL.', + 'Ingredient hierarchies — PizzaTopping and PizzaBase as independent classes, each with 5 and 2 leaf subclasses.', + 'hasPart ObjectProperty with rdfs:domain Pizza — ingredients stay semantically independent.', + 'Named pizza classes — SalamiPizza · HawaiianPizza · MargheritaPizza, each subClassOf Pizza.', + 'equivalentClass axioms loaded — pizza types defined by necessary-and-sufficient conditions.', + 'All TBox nodes expanded — asserted properties visible across the hierarchy.', + 'ABox view — pizza1 · pizza2 · pizza3 as bare NamedIndividuals, no class asserted.', + 'pizza1 built — salami1 · mozz1 · base1 typed and linked via hasPart.', + 'pizza2 (Hawaiian) and pizza3 (Margherita) built — all parts typed and linked.', + 'OWL-RL reasoning complete — inferred triples materialised in urn:vg:inferred.', + 'Classification! pizza1 → SalamiPizza · pizza2 → HawaiianPizza · pizza3 → MargheritaPizza — all inferred, none asserted.', +]; + +// T0–T10: Socratic arc guiding qwen3 through a rich pizza ontology. +// Named pizza types (SalamiPizza/HawaiianPizza/MargheritaPizza) with equivalentClass +// axioms; 3 untyped ABox pizzas classified by OWL-RL reasoning. +// Source of truth: .playwright/pizza-demo-setup.js (T0) + .playwright/turn-driver.js (T1–T10). +const TURNS = [ + // T0 — root class + 'I want to learn OWL ontology concepts through a hands-on example. I will guide you through the pizza domain step by step — one concept at a time. Rule: for each question I ask, model exactly the concept I ask about on the canvas, then stop and wait. Do not add anything beyond what I asked. Do not arrange nodes automatically. Use the ex: prefix for all IRIs (ex: maps to http://example.org/). First question: in OWL, what is the most fundamental building block for representing a concept? Create a single Pizza class — just this one node, nothing more. Wait for my next question.', + + // T1 — ingredient hierarchy: PizzaTopping + PizzaBase as INDEPENDENT classes (NOT subClassOf Pizza) + // PizzaTopping/PizzaBase must be siblings of Pizza, not children — avoids semantic leak where + // ingredients get inferred as Pizza via subclass chain or prp-range. + 'A pizza is made of two kinds of ingredient — a topping and a base. In OWL these form their own separate class hierarchies, distinct from the pizza itself. Add ex:PizzaTopping and ex:PizzaBase as independent owl:Class nodes — they are not a kind of pizza, so do not add any subClassOf edge to ex:Pizza. Then add five specific topping subclasses (each rdfs:subClassOf ex:PizzaTopping): ex:SalamiTopping, ex:HamTopping, ex:PineappleTopping, ex:MozzarellaTopping, ex:TomatoTopping. Add two base subclasses (each rdfs:subClassOf ex:PizzaBase): ex:ThinCrustBase, ex:DeepPanBase. All nodes and all subClassOf edges required. Then arrange the canvas. Wait for my next question.', + + // T2 — owl:ObjectProperty hasPart with rdfs:domain only (NO range) + // Range must be omitted — declaring range=Pizza would cause prp-range to infer that ingredients + // are pizzas, which is semantically wrong. Domain alone is sufficient for classification. + // CRITICAL: rdfs:domain — OWL-RL does NOT read owl:domain. + 'In OWL, the relationship between a pizza and its parts is an owl:ObjectProperty. Create ex:hasPart as an ObjectProperty on the canvas. Declare its domain using rdfs:domain pointing to ex:Pizza — this tells the reasoner that anything with a hasPart connection is a pizza. Do not declare a range — leaving it open keeps ingredients semantically clean. Important: use rdfs:domain, not owl:domain. Wait for my next question.', + + // T3 — named pizza subclasses: SalamiPizza, HawaiianPizza, MargheritaPizza + 'There are many specific kinds of pizza. Add three named pizza classes: ex:SalamiPizza, ex:HawaiianPizza, and ex:MargheritaPizza. Each is a subclass of ex:Pizza — add all three nodes and all three rdfs:subClassOf ex:Pizza edges. Then arrange the hierarchy. Wait for my next question.', + + // T4 — Socratic: owl:equivalentClass + owl:Restriction + owl:someValuesFrom. + // Characteristic toppings: Salami → SalamiTopping, Hawaiian → PineappleTopping, + // Margherita → TomatoTopping. Must match ABox individuals added in T7/T8. + 'In OWL a class can be defined by a necessary-and-sufficient condition using owl:equivalentClass and owl:Restriction with owl:someValuesFrom. Define each named pizza class with such a condition — SalamiPizza by its characteristic SalamiTopping, HawaiianPizza by PineappleTopping, MargheritaPizza by TomatoTopping. Wait for my next question.', + + // T5 — expandNode all + runLayout + 'Expand all class nodes on the canvas to reveal their asserted properties, then arrange. Wait for my next question.', + + // T6 — ABox: setViewMode + 3 untyped NamedIndividuals + // No class assertion — prp-domain (hasPart domain Pizza) will infer Pizza type. + 'Everything so far is the TBox — the schema. Switch to the individuals view (ABox) and create three pizza individuals: ex:pizza1, ex:pizza2, and ex:pizza3. Give each only the owl:NamedIndividual type — do NOT assert any pizza class (not Pizza, not SalamiPizza, nothing). Only the three bare nodes. The reasoner will classify them once we add ingredients. Arrange. Wait for my next question.', + + // T7 — build pizza1 (Salami): typed parts + hasPart connections + // SalamiTopping is the characteristic for SalamiPizza (T4 equivalentClass). + 'Build ex:pizza1 as a Salami pizza. Add three ingredient individuals: ex:salami1 of type ex:SalamiTopping, ex:mozz1 of type ex:MozzarellaTopping, and ex:base1 of type ex:ThinCrustBase. Then add an ex:hasPart edge FROM ex:pizza1 TO each ingredient (subject=pizza1, object=ingredient). Do not assert any pizza class on ex:pizza1 — leave it untyped; the reasoner will classify it. Wait for my next question.', + + // T8 — build pizza2 (Hawaiian) + pizza3 (Margherita) + layout + // PineappleTopping is characteristic for HawaiianPizza; TomatoTopping for MargheritaPizza. + 'Build ex:pizza2 as a Hawaiian pizza and ex:pizza3 as a Margherita pizza. For ex:pizza2: add ex:pineapple1 of type ex:PineappleTopping, ex:ham1 of type ex:HamTopping, and ex:base2 of type ex:DeepPanBase. Add ex:hasPart edges FROM ex:pizza2 TO each ingredient (subject=pizza2, object=ingredient). For ex:pizza3: add ex:tom1 of type ex:TomatoTopping, ex:mozz2 of type ex:MozzarellaTopping, and ex:base3 of type ex:ThinCrustBase. Add ex:hasPart edges FROM ex:pizza3 TO each ingredient (subject=pizza3, object=ingredient). Do not assert any class type on pizza2 or pizza3. Reveal all node properties and arrange the canvas. Wait for my next question.', + + // T9 — runReasoning + 'The schema and all three pizzas are in place. Now apply OWL-RL reasoning to derive everything that can be inferred. Wait for my next question.', + + // T10 — classification showcase via graph query + // queryGraph covers urn:vg:data + urn:vg:inferred by default — model should reach for it + // naturally when asked to "verify" classification. getNodeDetails is acceptable fallback. + // cls-svf1: pizza1 hasPart salami1 ∧ salami1 type SalamiTopping → pizza1 type _:restriction + // cax-eqc2: _:restriction subClassOf SalamiPizza → pizza1 type SalamiPizza + 'The equivalentClass axioms we defined in the TBox should have been applied consistently across all three pizzas. Verify that the classification held: did each pizza receive the type its ingredient composition implies? Use whatever tool gives you the clearest proof — querying the graph or inspecting individual nodes. Show the evidence, state which types are inferred vs asserted, and trace the OWL-RL rule chain for at least one pizza.', +]; + +// ── Helpers ──────────────────────────────────────────────────────────────────── + +const sleep = (ms: number) => new Promise(r => setTimeout(r, ms)); + +const demoStart = Date.now(); +function demoLog(...args: unknown[]) { + const s = ((Date.now() - demoStart) / 1000).toFixed(1); + console.log(`[demo +${s}s]`, ...args); +} + +async function waitForFrame(page: Page, urlPrefix: string, timeout = 60_000): Promise { + const deadline = Date.now() + timeout; + while (Date.now() < deadline) { + // parentFrame() !== null excludes the main frame when VG_URL matches both + const f = page.frames().find(fr => fr.url().startsWith(urlPrefix) && fr.parentFrame() !== null); + if (f) return f; + await sleep(500); + } + throw new Error(`Timeout waiting for frame with URL prefix: ${urlPrefix}`); +} + +async function caption(page: Page, text: string): Promise { + await page.evaluate((t) => { + let el = document.getElementById('__cap__') as HTMLElement | null; + if (!el) { + el = document.createElement('div'); + el.id = '__cap__'; + Object.assign(el.style, { + position: 'fixed', bottom: '36px', left: '50%', + transform: 'translateX(-50%)', + background: 'rgba(0,0,0,0.84)', color: '#fff', + font: '600 17px/1.45 "Inter",sans-serif', + padding: '13px 30px', borderRadius: '8px', + zIndex: '99999', pointerEvents: 'none', + maxWidth: '80%', textAlign: 'center', + boxShadow: '0 4px 28px rgba(0,0,0,0.45)', + }); + document.body.appendChild(el); + } + el.textContent = t; + el.style.display = 'block'; + }, text); +} + +async function clearCaption(page: Page): Promise { + await page.evaluate(() => { + const el = document.getElementById('__cap__'); + if (el) el.style.display = 'none'; + }); +} + +// Shared busy-check: content-length growth OR relay has queued work. +// isAiStreaming() is NOT used — newer OWUI keeps the send button enabled while +// generating so button-state detection gives false negatives. +// We walk the DOM skipping
subtrees so qwen3 chain-of-thought tokens +// (streamed into hidden thinking blocks) don't keep isBusy returning true after +// the visible response has settled. +async function isBusy(frame: Frame, prevLen: number): Promise<{ busy: boolean; len: number }> { + const state = await frame.evaluate(() => { + const relayBusy = !((window as any).__vgIsRelayIdle?.() ?? true); + // Walk body text nodes, skipping
subtrees (thinking/CoT blocks). + let len = 0; + function walk(node: Node) { + if (node.nodeType === Node.TEXT_NODE) { + len += (node.textContent ?? '').length; + } else if (node.nodeType === Node.ELEMENT_NODE) { + if ((node as Element).tagName === 'DETAILS') return; + for (const child of node.childNodes) walk(child); + } + } + walk(document.body); + return { relayBusy, len }; + }).catch(() => ({ relayBusy: false, len: prevLen })); + const growing = prevLen >= 0 && state.len !== prevLen; + return { busy: state.relayBusy || growing, len: state.len }; +} + +// Wait until quiet for stableMs. Used for SEED/INSTR phases (short window). +async function waitIdle(frame: Frame, timeout = 300_000, stableMs = 3_000): Promise { + const deadline = Date.now() + timeout; + const pollMs = 500; + let silentMs = 0; + let lastLen = -1; + while (Date.now() < deadline) { + const { busy, len } = await isBusy(frame, lastLen); + lastLen = len; + if (busy) silentMs = 0; else silentMs += pollMs; + if (silentMs >= stableMs) return true; + await sleep(pollMs); + } + return false; +} + +// Wait for quietMs of continuous silence — relay fully drained, no content growth. +// Resets if the model spontaneously makes more calls during the gap. +// maxMs caps the total wait so OWUI background updates (thinking dots, timestamps) +// can't reset the timer indefinitely and stall the run. +async function waitQuiet(frame: Frame, quietMs = 10_000, maxMs = 45_000): Promise { + const pollMs = 500; + let silentMs = 0; + let lastLen = -1; + const deadline = Date.now() + maxMs; + while (silentMs < quietMs && Date.now() < deadline) { + const { busy, len } = await isBusy(frame, lastLen); + lastLen = len; + if (busy) silentMs = 0; else silentMs += pollMs; + await sleep(pollMs); + } +} + +async function inject(frame: Frame, text: string, retries = 8): Promise { + for (let i = 0; i < retries; i++) { + const ok = await frame.evaluate( + (t) => typeof (window as any).__vgInjectResult === 'function' + ? (window as any).__vgInjectResult(t) : false, + text, + ).catch(() => false); + if (ok !== false) return true; + await sleep(500); + } + return false; +} + +async function clickSend(frame: Frame): Promise { + await frame.locator('#send-message-button:not([disabled])').click({ timeout: 3_000 }).catch(() => {}); +} + +// Type text character-by-character into the chat input and press Enter. +// Looks like the user is typing — makes the demo easy to follow. +async function typeAndSend(frame: Frame, page: Page, text: string): Promise { + await frame.locator('#chat-input').click(); + const lines = text.split('\n'); + for (let i = 0; i < lines.length; i++) { + if (lines[i]) await page.keyboard.type(lines[i], { delay: 18 }); + if (i < lines.length - 1) await page.keyboard.press('Shift+Enter'); + } + await sleep(400); + await page.keyboard.press('Enter'); +} + +// ── Test ────────────────────────────────────────────────────────────────────── + +test('openwebui-socratic: Socratic pizza ontology — live qwen3:4b via OWUI relay', async ({ page, context }) => { + test.setTimeout(2_700_000); // 45 min — qwen3 reasoning turns can be slow + + // Expose a logging bridge callable from ANY frame (including iframes). + // appFrame.on('console') doesn't work for iframes — use exposeFunction instead. + await context.exposeFunction('__demoLog__', (msg: string) => { demoLog(msg); }); + + // ── 1. Restore cookies; register init script for localStorage ────────────── + if (fs.existsSync(AUTH_FILE)) { + const state = JSON.parse(fs.readFileSync(AUTH_FILE, 'utf8')) as { + cookies?: Array>; + origins?: Array<{ origin: string; localStorage: Array<{ name: string; value: string }> }>; + }; + if (state.cookies?.length) + await context.addCookies(state.cookies as unknown as Parameters[0]); + if (state.origins?.length) { + await context.addInitScript((origins) => { + const entry = (origins as Array<{ origin: string; localStorage: Array<{ name: string; value: string }> }>) + .find(o => o.origin === location.origin); + if (entry?.localStorage) + for (const { name, value } of entry.localStorage) + try { localStorage.setItem(name, value); } catch { /* ignore */ } + }, state.origins); + } + } + + // ── 2. Pre-auth throwaway pages ─────────────────────────────────────────── + // addInitScript only runs in top-level frames, not iframes. Navigate to each + // origin on a real page first — this seeds localStorage for both OWUI and the + // Ontosphere app in the shared context storage. Their iframes then read it. + demoLog('step 1: auth pre-seed — navigating to OWUI'); + const authPage = await context.newPage(); + await authPage.goto(`${OWUI_URL}/`); + await authPage.waitForSelector('#chat-input', { timeout: 60_000 }); + await authPage.close(); + demoLog('step 1: OWUI auth OK'); + + const appAuthPage = await context.newPage(); + await appAuthPage.goto(`${VG_URL}/`); + await appAuthPage.waitForFunction(() => !!(window as any).__mcpTools?.addNode, { timeout: 30_000 }); + await appAuthPage.evaluate(() => { + localStorage.setItem('ontology-painter-config', JSON.stringify({ + config: { autoApplyLayout: true, workflowCatalogEnabled: false }, + })); + }); + await appAuthPage.close(); + demoLog('step 1: app auth OK'); + + // ── 3. Load side-by-side stage (this is the recorded page) ──────────────── + // ?ontologies=owl,rdf,rdfs on the app URL replaces the 6 default additionalOntologies + // (PROV/P-PLAN/QUDT + core W3C) with only the 3 W3C vocabs needed for OWL-RL reasoning. + const appUrl = `${VG_URL}/?ontologies=${encodeURIComponent('owl,rdf,rdfs')}`; + const stageUrl = `${VG_URL}/demo-stage-owui.html` + + `?owui=${encodeURIComponent(OWUI_URL + '/')}` + + `&app=${encodeURIComponent(appUrl)}`; + demoLog('step 2: loading stage page'); + await page.goto(stageUrl); + await caption(page, 'Loading Ontosphere × OpenWebUI demo stage…'); + + // ── 4. Wait for both iframes to become active ───────────────────────────── + const appFrame = await waitForFrame(page, VG_URL); + const chatFrame = await waitForFrame(page, OWUI_URL, 90_000); + await appFrame.waitForFunction(() => !!(window as any).__mcpTools?.addNode, { timeout: 30_000 }); + await chatFrame.locator('#chat-input').waitFor({ timeout: 90_000 }); + demoLog('step 3: both frames ready'); + // Hide broken model avatar images — profile pic URLs don't resolve in the iframe context, + // leaving a broken-image placeholder next to every model response. MutationObserver ensures + // dynamically added images (new chat messages) are also caught. + await chatFrame.evaluate(() => { + const hide = (img: HTMLImageElement) => { img.style.display = 'none'; }; + document.querySelectorAll('img').forEach(img => { + img.addEventListener('error', () => hide(img), { once: true }); + if (img.complete && img.naturalWidth === 0) hide(img); + }); + new MutationObserver(muts => { + for (const m of muts) for (const n of m.addedNodes) { + if ((n as Element).querySelectorAll) + (n as Element).querySelectorAll('img').forEach(img => + img.addEventListener('error', () => hide(img), { once: true }) + ); + } + }).observe(document.body, { childList: true, subtree: true }); + }); + await sleep(1_500); + + // ── 5. Select model ──────────────────────────────────────────────────────── + while (true) { + const rm = await chatFrame.$('button[aria-label*="Remove Model"]'); + if (!rm) break; + await rm.click(); + await sleep(300); + } + const modelBtn = await chatFrame.$('#model-selector-0-button'); + if (modelBtn) { + await modelBtn.click(); + await sleep(400); + const search = await chatFrame.$('input[placeholder*="Search" i]'); + if (search) { await search.fill(MODEL); await sleep(400); } + const pick = await chatFrame.$(`button:has-text("${MODEL}")`); + if (pick) { await pick.click(); await sleep(400); } + } + + // ── 6. Type full README starter prompt → creates /c/ URL ────────────────── + // Source of truth: README.md "Starter prompt" section. + // Typed line-by-line with Shift+Enter — same mechanism as Socratic questions. + // The embedded id:0 help() call is pre-seeded by the relay on startup and will + // NOT be re-executed. Only the model's own help() call in its response fires. + await caption(page, 'Sending relay starter prompt…'); + const STARTER_LINES = [ + 'You are connected to Ontosphere via a relay. A script in this tab intercepts your tool calls, runs them in Ontosphere, and injects results back as a user message. Always demonstrate answers by BUILDING in Ontosphere — never describe what you would do, always do it. Every response to a question about a concept must include tool calls that construct that concept in the graph. If a tool call returns success:false, read the error, fix the argument, and retry the same call immediately — never skip a failed call. Ask the user what they would like to build.', + '', + 'Output format — one JSON-RPC 2.0 call per line, backtick-wrapped:', + '`{"jsonrpc":"2.0","id":,"method":"tools/call","params":{"name":"","arguments":{...}}}`', + '', + 'Call help first to get full instructions and the tool list:', + '`{"jsonrpc":"2.0","id":0,"method":"tools/call","params":{"name":"help","arguments":{}}}`', + ]; + await chatFrame.locator('#chat-input').click(); + for (let i = 0; i < STARTER_LINES.length; i++) { + if (STARTER_LINES[i]) await page.keyboard.type(STARTER_LINES[i], { delay: 2 }); + if (i < STARTER_LINES.length - 1) await page.keyboard.press('Shift+Enter'); + } + demoLog('step 5: sending starter prompt'); + await page.keyboard.press('Enter'); + await chatFrame.waitForFunction(() => location.pathname.startsWith('/c/'), { timeout: 15_000 }); + demoLog('step 5: starter prompt sent, chat URL:', await chatFrame.evaluate(() => location.pathname)); + + // ── 7. Fetch relay code from appFrame (HTTP — no mixed-content) ─────────── + const relayCode = await appFrame.evaluate(async (vgUrl: string) => { + const r = await fetch('/relay-bookmarklet.js'); + let src: string = await r.text(); + src = src.replace(/__RELAY_URL__/g, `${vgUrl}/relay.html`); + src = src.replace(/__RELAY_ORIGIN__/g, vgUrl); + src = src.replace(/\}\)\(\);\s*$/, [ + ' window.__vgInjectResult = injectResult;', + ' window.__vgIsStreaming = isAiStreaming;', + ' window.__vgWaitForIdle = waitForIdle;', + ' window.__vgIsRelayIdle = function() { return callQueue.length === 0 && !isProcessing; };', + '})();', + ].join('\n')); + return src; + }, VG_URL); + + // ── 8. Inject relay into OWUI frame — opens relay popup ─────────────────── + const relayPopupPromise = context.waitForEvent('page', { timeout: 20_000 }); + await chatFrame.evaluate((src: string) => { new Function(src)(); }, relayCode); + const relayPopup = await relayPopupPromise; + await relayPopup.waitForLoadState('domcontentloaded'); + await sleep(500); + + // Wrap __mcpTools in the app frame so every tool call + result is logged. + // Uses __demoLog__ (exposeFunction) — the only reliable way to get data out + // of an iframe since frame.on('console') doesn't fire for subframe messages. + await appFrame.evaluate(() => { + const log = (window as any).__demoLog__ as (msg: string) => void; + const tools = (window as any).__mcpTools as Record Promise>; + for (const name of Object.keys(tools)) { + const orig = tools[name]; + tools[name] = async (params: unknown) => { + const argStr = JSON.stringify(params ?? {}); + log(`[MCP→] ${name} ${argStr.length > 600 ? argStr.slice(0, 600) + '…' : argStr}`); + const result = await orig(params); + const resStr = JSON.stringify(result ?? {}); + log(`[MCP←] ${name} ${resStr.length > 300 ? resStr.slice(0, 300) + '…' : resStr}`); + return result; + }; + } + }); + + // ── 9. Wait for model's help() cycle to complete ────────────────────────── + // Model reads starter prompt → calls help() → relay executes → model reads manifest + // and may make follow-up calls. waitQuiet(15 000) exits only after 15 consecutive + // seconds of complete silence (no relay work, no content growth) — prevents + // accidentally firing during a brief pause between tool calls. 3-min cap cuts out + // if the model gets stuck without producing output for the full window. + demoLog('step 6: relay injected — waiting for model help() cycle (up to 3 min)'); + await caption(page, 'Model familiarising with MCP tools — calling help()…'); + await waitQuiet(chatFrame, 15_000, 180_000); + demoLog('step 6: help() cycle done — 15 s silent or 3 min elapsed'); + // Brief pause so viewers see the model's response before Socratic turns begin. + await sleep(3_000); + await clearCaption(page); + + // ── 10. Socratic turns ──────────────────────────────────────────────────── + await clearCaption(page); + for (let i = 0; i < TURNS.length; i++) { + demoLog(`turn ${i + 1}/${TURNS.length}: "${TURN_TOPICS[i]}" — sending`); + // Before: brief label so viewer knows what concept is being asked about + await caption(page, TURN_TOPICS[i]); + await sleep(2_000); + + // Inject via relay for multi-line turns (avoids choppy Shift+Enter typing + // animation); fall back to typeAndSend for single-line questions. + const isMultiLine = TURNS[i].includes('\n'); + if (isMultiLine) { + await inject(chatFrame, TURNS[i]); + await sleep(400); + await clickSend(chatFrame); + } else { + await typeAndSend(chatFrame, page, TURNS[i]); + } + + // Clear caption while model generates — the live chat + canvas are the show + await clearCaption(page); + // 10s stable window — ensures relay queue is fully drained and model is + // truly done before declaring idle between turns. + await waitIdle(chatFrame, 300_000, 10_000); + demoLog(`turn ${i + 1}/${TURNS.length}: idle — model done`); + + // Caption goes up the moment the model first goes idle so viewers can read + // what was built. waitQuiet holds for 15 s of continuous silence (was 10 s) + // to avoid cutting before late tool calls finish. + await caption(page, AFTER_CAPTIONS[i]); + await waitQuiet(chatFrame, 15_000); + await clearCaption(page); + await sleep(2_000); + } + + // ── 11. Export Turtle snapshot ─────────────────────────────────────────── + try { + const turtleData = await appFrame.evaluate(async () => { + const tools = (window as any).__mcpTools; + if (!tools?.exportGraph) return null; + const result = await tools.exportGraph({ format: 'turtle' }); + return result?.data?.content ?? null; + }); + if (turtleData) { + const turtlePath = path.resolve(__dirname, '../logs/demo-last-run-data.ttl'); + fs.writeFileSync(turtlePath, turtleData, 'utf8'); + demoLog(`turtle exported → logs/demo-last-run-data.ttl (${turtleData.length} chars)`); + } else { + demoLog('turtle export skipped — rdfManager not available'); + } + } catch (e) { + demoLog('turtle export failed:', e); + } + + // ── 12. End card ────────────────────────────────────────────────────────── + demoLog('all turns done — end card'); + await caption(page, 'Pizza ontology — TBox · ABox · named pizza classes · equivalentClass axioms · OWL-RL classification — built through Socratic questioning alone'); + await sleep(6_000); + await clearCaption(page); + + // ── 13. Save video ──────────────────────────────────────────────────────── + // Use video.saveAs() — more reliable than path()+copyFileSync because path() + // can return the transient artifacts path which Playwright moves before we read it. + const video = page.video(); + const videoOutDir = path.resolve(__dirname, '../test-results/demo/demo-openwebui-socratic-op-52070-ive-qwen3-4b-via-OWUI-relay-openwebui-demo'); + fs.mkdirSync(videoOutDir, { recursive: true }); + const videoPath = path.join(videoOutDir, 'video.webm'); + await relayPopup.close().catch(() => {}); + await page.close(); + await video?.saveAs(videoPath).catch((e) => demoLog('video saveAs failed:', e)); + if (fs.existsSync(videoPath)) { + const mb = (fs.statSync(videoPath).size / 1024 / 1024).toFixed(1); + demoLog(`video saved → ${videoPath} (${mb} MB)`); + } else { + demoLog('video unavailable — saveAs did not produce a file'); + } +}); diff --git a/e2e/demo-pizza-tutorial-chat.spec.ts b/e2e/demo-pizza-tutorial-chat.spec.ts index 7c884891..24d12702 100644 --- a/e2e/demo-pizza-tutorial-chat.spec.ts +++ b/e2e/demo-pizza-tutorial-chat.spec.ts @@ -63,9 +63,9 @@ const TURNS: Turn[] = [ user: "So Pizza, PizzaBase, and PizzaTopping are all separate classes — but how does OWL know they're truly different things, not overlapping?", ai: "Great question — by default OWL makes no assumption that different classes are separate, so a thing could technically be both a Pizza and a PizzaTopping unless you explicitly forbid it. That's what owl:disjointWith does: it tells the reasoner that no individual can belong to both classes at once. I'll now declare all three root classes pairwise disjoint so they are guaranteed to be mutually exclusive.", tools: [ - ['addLink', disj('Pizza', 'PizzaBase')], - ['addLink', disj('Pizza', 'PizzaTopping')], - ['addLink', disj('PizzaBase', 'PizzaTopping')], + ['addTriple', disj('Pizza', 'PizzaBase')], + ['addTriple', disj('Pizza', 'PizzaTopping')], + ['addTriple', disj('PizzaBase', 'PizzaTopping')], layout('elk-layered', 200), ], viewMs: 2500, @@ -78,9 +78,9 @@ const TURNS: Turn[] = [ tools: [ ['addNode', cls('ThinAndCrispyBase')], ['addNode', cls('DeepPanBase')], - ['addLink', sub('ThinAndCrispyBase', 'PizzaBase')], - ['addLink', sub('DeepPanBase', 'PizzaBase')], - ['addLink', disj('ThinAndCrispyBase', 'DeepPanBase')], + ['addTriple', sub('ThinAndCrispyBase', 'PizzaBase')], + ['addTriple', sub('DeepPanBase', 'PizzaBase')], + ['addTriple', disj('ThinAndCrispyBase', 'DeepPanBase')], layout('elk-layered', 170), ], viewMs: 2500, @@ -95,10 +95,10 @@ const TURNS: Turn[] = [ ['addNode', cls('Margherita')], ['addNode', cls('AmericanHot')], ['addNode', cls('FruttiDiMare')], - ['addLink', sub('NamedPizza', 'Pizza')], - ['addLink', sub('Margherita', 'NamedPizza')], - ['addLink', sub('AmericanHot', 'NamedPizza')], - ['addLink', sub('FruttiDiMare', 'NamedPizza')], + ['addTriple', sub('NamedPizza', 'Pizza')], + ['addTriple', sub('Margherita', 'NamedPizza')], + ['addTriple', sub('AmericanHot', 'NamedPizza')], + ['addTriple', sub('FruttiDiMare', 'NamedPizza')], layout('elk-layered', 150), ], viewMs: 2500, @@ -113,10 +113,10 @@ const TURNS: Turn[] = [ ['addNode', cls('MeatTopping')], ['addNode', cls('VegetableTopping')], ['addNode', cls('FishTopping')], - ['addLink', sub('CheeseTopping', 'PizzaTopping')], - ['addLink', sub('MeatTopping', 'PizzaTopping')], - ['addLink', sub('VegetableTopping', 'PizzaTopping')], - ['addLink', sub('FishTopping', 'PizzaTopping')], + ['addTriple', sub('CheeseTopping', 'PizzaTopping')], + ['addTriple', sub('MeatTopping', 'PizzaTopping')], + ['addTriple', sub('VegetableTopping', 'PizzaTopping')], + ['addTriple', sub('FishTopping', 'PizzaTopping')], layout('elk-layered', 140), ], viewMs: 2500, @@ -127,12 +127,12 @@ const TURNS: Turn[] = [ user: "Why do those topping categories also need disjointWith? Isn't it obvious that cheese isn't meat?", ai: "It may seem obvious to us, but OWL follows the Open World Assumption — it never assumes something is false just because it hasn't been stated. Without disjointness declarations, a reasoner is happy to conclude that mozzarella could simultaneously be a CheeseTopping and a MeatTopping. I'll now add all six pairwise disjointWith assertions between the four topping categories.", tools: [ - ['addLink', disj('CheeseTopping', 'MeatTopping')], - ['addLink', disj('CheeseTopping', 'VegetableTopping')], - ['addLink', disj('CheeseTopping', 'FishTopping')], - ['addLink', disj('MeatTopping', 'VegetableTopping')], - ['addLink', disj('MeatTopping', 'FishTopping')], - ['addLink', disj('VegetableTopping', 'FishTopping')], + ['addTriple', disj('CheeseTopping', 'MeatTopping')], + ['addTriple', disj('CheeseTopping', 'VegetableTopping')], + ['addTriple', disj('CheeseTopping', 'FishTopping')], + ['addTriple', disj('MeatTopping', 'VegetableTopping')], + ['addTriple', disj('MeatTopping', 'FishTopping')], + ['addTriple', disj('VegetableTopping', 'FishTopping')], layout('elk-layered', 140), ], viewMs: 2500, @@ -150,13 +150,13 @@ const TURNS: Turn[] = [ ['addNode', cls('OliveTopping')], ['addNode', cls('GarlicTopping')], ['addNode', cls('AnchoviesTopping')], - ['addLink', sub('MozzarellaTopping', 'CheeseTopping')], - ['addLink', sub('ParmesanTopping', 'CheeseTopping')], - ['addLink', sub('PeperoniSausageTopping','MeatTopping')], - ['addLink', sub('TomatoTopping', 'VegetableTopping')], - ['addLink', sub('OliveTopping', 'VegetableTopping')], - ['addLink', sub('GarlicTopping', 'VegetableTopping')], - ['addLink', sub('AnchoviesTopping', 'FishTopping')], + ['addTriple', sub('MozzarellaTopping', 'CheeseTopping')], + ['addTriple', sub('ParmesanTopping', 'CheeseTopping')], + ['addTriple', sub('PeperoniSausageTopping','MeatTopping')], + ['addTriple', sub('TomatoTopping', 'VegetableTopping')], + ['addTriple', sub('OliveTopping', 'VegetableTopping')], + ['addTriple', sub('GarlicTopping', 'VegetableTopping')], + ['addTriple', sub('AnchoviesTopping', 'FishTopping')], EXPAND, layout('elk-layered', 130), ], @@ -170,10 +170,10 @@ const TURNS: Turn[] = [ tools: [ ['addNode', prop('hasTopping')], ['addNode', prop('hasBase')], - ['addLink', dom('hasTopping', 'Pizza')], - ['addLink', ran('hasTopping', 'PizzaTopping')], - ['addLink', dom('hasBase', 'Pizza')], - ['addLink', ran('hasBase', 'PizzaBase')], + ['addTriple', dom('hasTopping', 'Pizza')], + ['addTriple', ran('hasTopping', 'PizzaTopping')], + ['addTriple', dom('hasBase', 'Pizza')], + ['addTriple', ran('hasBase', 'PizzaBase')], layout('elk-layered', 130), ], viewMs: 2500, @@ -186,12 +186,12 @@ const TURNS: Turn[] = [ tools: [ ['addNode', prop('isToppingOf')], ['addNode', prop('isBaseOf')], - ['addLink', inv('isToppingOf', 'hasTopping')], - ['addLink', inv('isBaseOf', 'hasBase')], - ['addLink', dom('isToppingOf', 'PizzaTopping')], - ['addLink', ran('isToppingOf', 'Pizza')], - ['addLink', dom('isBaseOf', 'PizzaBase')], - ['addLink', ran('isBaseOf', 'Pizza')], + ['addTriple', inv('isToppingOf', 'hasTopping')], + ['addTriple', inv('isBaseOf', 'hasBase')], + ['addTriple', dom('isToppingOf', 'PizzaTopping')], + ['addTriple', ran('isToppingOf', 'Pizza')], + ['addTriple', dom('isBaseOf', 'PizzaBase')], + ['addTriple', ran('isBaseOf', 'Pizza')], EXPAND, layout('elk-layered', 130), ], @@ -237,9 +237,9 @@ const TURNS: Turn[] = [ ['addNode', ind('mozz1', 'MozzarellaTopping', 'mozz')], ['addNode', ind('tom1', 'TomatoTopping', 'tomato')], ['addNode', ind('thin1', 'ThinAndCrispyBase', 'thin & crispy')], - ['addLink', link('pizza1', 'hasTopping', 'mozz1')], - ['addLink', link('pizza1', 'hasTopping', 'tom1')], - ['addLink', link('pizza1', 'hasBase', 'thin1')], + ['addTriple', link('pizza1', 'hasTopping', 'mozz1')], + ['addTriple', link('pizza1', 'hasTopping', 'tom1')], + ['addTriple', link('pizza1', 'hasBase', 'thin1')], layout('dagre-lr', 190), ['focusNode', { iri: P + 'pizza1' }], ], @@ -255,10 +255,10 @@ const TURNS: Turn[] = [ ['addNode', ind('mozz2', 'MozzarellaTopping', 'mozz')], ['addNode', ind('olive1', 'OliveTopping', 'olive')], ['addNode', ind('deep1', 'DeepPanBase', 'deep pan')], - ['addLink', link('pizza2', 'hasTopping', 'pep1')], - ['addLink', link('pizza2', 'hasTopping', 'mozz2')], - ['addLink', link('pizza2', 'hasTopping', 'olive1')], - ['addLink', link('pizza2', 'hasBase', 'deep1')], + ['addTriple', link('pizza2', 'hasTopping', 'pep1')], + ['addTriple', link('pizza2', 'hasTopping', 'mozz2')], + ['addTriple', link('pizza2', 'hasTopping', 'olive1')], + ['addTriple', link('pizza2', 'hasBase', 'deep1')], layout('dagre-lr', 190), ['focusNode', { iri: P + 'pizza2' }], ], @@ -273,9 +273,9 @@ const TURNS: Turn[] = [ ['addNode', ind('anch1', 'AnchoviesTopping', 'anchovies')], ['addNode', ind('garlic1', 'GarlicTopping', 'garlic')], ['addNode', ind('thin2', 'ThinAndCrispyBase', 'thin & crispy')], - ['addLink', link('pizza3', 'hasTopping', 'anch1')], - ['addLink', link('pizza3', 'hasTopping', 'garlic1')], - ['addLink', link('pizza3', 'hasBase', 'thin2')], + ['addTriple', link('pizza3', 'hasTopping', 'anch1')], + ['addTriple', link('pizza3', 'hasTopping', 'garlic1')], + ['addTriple', link('pizza3', 'hasBase', 'thin2')], EXPAND, layout('dagre-lr', 190), ['focusNode', { iri: P + 'pizza3' }], diff --git a/e2e/reasoning-named-restriction.spec.ts b/e2e/reasoning-named-restriction.spec.ts new file mode 100644 index 00000000..26ee39d3 --- /dev/null +++ b/e2e/reasoning-named-restriction.spec.ts @@ -0,0 +1,157 @@ +/** + * Named-restriction MCP store inspection test. + * + * Seeds the exact same triples as the reasoning unit test (named-node variant) + * via window.__mcpTools (addNode + addTriple), dumps the raw urn:vg:data quads, + * runs reasoning, then checks ind1 classification. + * + * Purpose: verify that MCP tools write exactly the triples we expect and that + * named restriction nodes reason correctly end-to-end in the live app. + * + * Requires: npm run dev (http://localhost:8080) + */ + +import { test, expect, Page } from '@playwright/test'; + +const BASE_URL = process.env.VG_URL ?? 'http://localhost:8080'; +const EX = 'http://example.org/collapse-test#'; +const OWL = 'http://www.w3.org/2002/07/owl#'; +const RDF = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'; + +async function waitForMcpTools(page: Page) { + await page.waitForFunction( + () => !!(window as any).__mcpTools && typeof (window as any).__mcpTools['addNode'] === 'function', + { timeout: 20_000 }, + ); +} + +async function call(page: Page, tool: string, params: object) { + return page.evaluate( + ([t, p]) => (window as any).__mcpTools[t](p), + [tool, params] as const, + ); +} + +test('loadRdf blank-node restrictions: skolemized on canvas, reasoning classifies correctly', async ({ page }) => { + await page.goto(BASE_URL); + await waitForMcpTools(page); + + const turtle = ` +@prefix ex: . +@prefix owl: . +@prefix rdf: . + +ex:ClassA owl:equivalentClass [ + rdf:type owl:Restriction ; + owl:onProperty ex:hasPart ; + owl:someValuesFrom ex:FillerA +] . + +ex:ClassB owl:equivalentClass [ + rdf:type owl:Restriction ; + owl:onProperty ex:hasPart ; + owl:someValuesFrom ex:FillerB +] . + +ex:ind1 rdf:type owl:NamedIndividual ; + ex:hasPart ex:p1 . + +ex:p1 rdf:type ex:FillerA . +`; + + await call(page, 'loadRdf', { turtle }); + + const linksResult = await call(page, 'getLinks', { limit: 500 }) as any; + const dataQuads: Array<{ subject: string; predicate: string; object: string }> = + linksResult?.data?.links ?? []; + + // No raw blank-node subjects in the store + const rawBnodes = dataQuads.filter(q => q.subject.startsWith('_:')); + expect(rawBnodes).toHaveLength(0); + + // Skolemized restriction nodes present + const skolemNodes = dataQuads.filter(q => q.subject.startsWith('urn:vg:bnode:')); + expect(skolemNodes.length).toBeGreaterThan(0); + + // Run reasoning and verify ind1 → ClassA only + await call(page, 'runReasoning', { rulesets: ['owl-rl.n3'] }); + + const details = await call(page, 'getNodeDetails', { iri: `${EX}ind1` }) as any; + const types: string[] = details?.data?.types ?? []; + + expect(types).toContain(`${EX}ClassA`); + expect(types).not.toContain(`${EX}ClassB`); +}); + +test('named restriction nodes: MCP seeds correct triples and reasoning classifies correctly', async ({ page }) => { + await page.goto(BASE_URL); + await waitForMcpTools(page); + + // ── Seed TBox: named restriction nodes ───────────────────────────────── + + // R1: someValuesFrom FillerA + await call(page, 'addNode', { iri: `${EX}R1`, typeIri: `${OWL}Restriction` }); + await call(page, 'addTriple', { subjectIri: `${EX}R1`, predicateIri: `${OWL}onProperty`, objectIri: `${EX}hasPart` }); + await call(page, 'addTriple', { subjectIri: `${EX}R1`, predicateIri: `${OWL}someValuesFrom`, objectIri: `${EX}FillerA` }); + + // R2: someValuesFrom FillerB (same onProperty, different filler) + await call(page, 'addNode', { iri: `${EX}R2`, typeIri: `${OWL}Restriction` }); + await call(page, 'addTriple', { subjectIri: `${EX}R2`, predicateIri: `${OWL}onProperty`, objectIri: `${EX}hasPart` }); + await call(page, 'addTriple', { subjectIri: `${EX}R2`, predicateIri: `${OWL}someValuesFrom`, objectIri: `${EX}FillerB` }); + + // ClassA ≡ R1, ClassB ≡ R2 + await call(page, 'addNode', { iri: `${EX}ClassA`, typeIri: `${OWL}Class` }); + await call(page, 'addNode', { iri: `${EX}ClassB`, typeIri: `${OWL}Class` }); + await call(page, 'addTriple', { subjectIri: `${EX}ClassA`, predicateIri: `${OWL}equivalentClass`, objectIri: `${EX}R1` }); + await call(page, 'addTriple', { subjectIri: `${EX}ClassB`, predicateIri: `${OWL}equivalentClass`, objectIri: `${EX}R2` }); + + // ── Seed ABox ─────────────────────────────────────────────────────────── + + // ind1 hasPart p1; p1 type FillerA only + await call(page, 'addNode', { iri: `${EX}ind1`, typeIri: `${OWL}NamedIndividual` }); + await call(page, 'addNode', { iri: `${EX}p1`, typeIri: `${EX}FillerA` }); + await call(page, 'addTriple', { subjectIri: `${EX}ind1`, predicateIri: `${EX}hasPart`, objectIri: `${EX}p1` }); + + // ── Dump urn:vg:data to inspect what actually landed ─────────────────── + + const linksResult = await call(page, 'getLinks', { limit: 500 }) as any; + const dataQuads: Array<{ subject: string; predicate: string; object: string }> = + linksResult?.data?.links ?? []; + + console.log('[TEST] urn:vg:data quads after seeding:'); + for (const q of dataQuads) console.log(' ', q.subject, q.predicate, q.object); + + // Verify key triples are present + const has = (s: string, p: string, o: string) => + dataQuads.some(q => q.subject === s && q.predicate === p && q.object === o); + + expect(has(`${EX}R1`, `${RDF}type`, `${OWL}Restriction`)).toBe(true); + expect(has(`${EX}R1`, `${OWL}onProperty`, `${EX}hasPart`)).toBe(true); + expect(has(`${EX}R1`, `${OWL}someValuesFrom`, `${EX}FillerA`)).toBe(true); + expect(has(`${EX}R2`, `${RDF}type`, `${OWL}Restriction`)).toBe(true); + expect(has(`${EX}R2`, `${OWL}onProperty`, `${EX}hasPart`)).toBe(true); + expect(has(`${EX}R2`, `${OWL}someValuesFrom`, `${EX}FillerB`)).toBe(true); + expect(has(`${EX}ClassA`, `${OWL}equivalentClass`, `${EX}R1`)).toBe(true); + expect(has(`${EX}ClassB`, `${OWL}equivalentClass`, `${EX}R2`)).toBe(true); + expect(has(`${EX}p1`, `${RDF}type`, `${EX}FillerA`)).toBe(true); + expect(has(`${EX}ind1`, `${EX}hasPart`, `${EX}p1`)).toBe(true); + + // ── Run OWL-RL reasoning ──────────────────────────────────────────────── + + const reasoningResult = await call(page, 'runReasoning', { rulesets: ['owl-rl.n3'] }); + console.log('[TEST] Reasoning result:', JSON.stringify(reasoningResult)); + + // ── Inspect ind1 after reasoning ──────────────────────────────────────── + + const details = await call(page, 'getNodeDetails', { iri: `${EX}ind1` }) as any; + console.log('[TEST] ind1 details after reasoning:', JSON.stringify(details)); + + const types: string[] = details?.data?.types ?? []; + console.log('[TEST] ind1 inferred types:', types); + + // ind1 must be classified as ClassA (has FillerA filler) + expect(types).toContain(`${EX}ClassA`); + + // ind1 must NOT be classified as ClassB (no FillerB filler) + expect(types).not.toContain(`${EX}ClassB`); +}); diff --git a/package.json b/package.json index 7340ff5a..fcc5cdd7 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "demo:scene": "node scripts/run-demo.mjs docs/mcp-demo/seeds/scene-ontology.md", "demo:pizza": "node scripts/run-demo.mjs docs/mcp-demo/seeds/pizza-tutorial.md", "demo:all": "npm run demo:reasoning && npm run demo:foaf && npm run demo:scene && npm run demo:pizza && node scripts/update-demo-table.mjs", + "demo:owui:auth": "node scripts/owui-auth.mjs", + "demo:owui:video": "xvfb-run --auto-servernum --server-args='-screen 0 1920x1080x24' npx playwright test --config=playwright.openwebui.config.ts && node scripts/collect-demo-videos.mjs", "callgraph:generate": "node scripts/generate_call_graph_ts_morph.js", "callgraph:merge": "node scripts/merge_call_graphs.js", "callgraph:regen": "npm run callgraph:generate && npm run callgraph:merge", diff --git a/playwright.demo.config.ts b/playwright.demo.config.ts index f18811f3..a69bba71 100644 --- a/playwright.demo.config.ts +++ b/playwright.demo.config.ts @@ -2,7 +2,7 @@ import { defineConfig } from '@playwright/test'; export default defineConfig({ testDir: './e2e', - testMatch: 'demo-*.spec.ts', + testMatch: /demo-(?!openwebui-socratic).*\.spec\.ts$/, outputDir: 'test-results/demo', timeout: 600_000, fullyParallel: false, @@ -15,6 +15,9 @@ export default defineConfig({ viewport: { width: 1920, height: 1080 }, video: { mode: 'on', size: { width: 1920, height: 1080 } }, baseURL: 'http://localhost:8080', + launchOptions: { + args: ['--disable-features=BlockInsecurePrivateNetworkRequests'], + }, }, }, ], diff --git a/playwright.openwebui.config.ts b/playwright.openwebui.config.ts new file mode 100644 index 00000000..605b33f0 --- /dev/null +++ b/playwright.openwebui.config.ts @@ -0,0 +1,26 @@ +import { defineConfig } from '@playwright/test'; + +export default defineConfig({ + testDir: './e2e', + testMatch: 'demo-openwebui-*.spec.ts', + outputDir: 'test-results/demo', + timeout: 600_000, + fullyParallel: false, + projects: [ + { + name: 'openwebui-demo', + use: { + browserName: 'chromium', + headless: false, + viewport: { width: 1920, height: 1080 }, + video: { mode: 'on', size: { width: 1920, height: 1080 } }, + baseURL: 'http://localhost:8080', + ignoreHTTPSErrors: true, + launchOptions: { + // Required: allows cross-origin iframe access and self-signed TLS + args: ['--disable-web-security', '--ignore-certificate-errors', '--disable-features=BlockInsecurePrivateNetworkRequests'], + }, + }, + }, + ], +}); diff --git a/public/.well-known/mcp.json b/public/.well-known/mcp.json index d8feb30d..9528fce9 100644 --- a/public/.well-known/mcp.json +++ b/public/.well-known/mcp.json @@ -1,6 +1,6 @@ { "name": "Ontosphere", - "description": "Browser-based RDF/ontology knowledge graph editor. ABox authoring, OWL-RL reasoning, layout, and export. All client-side, no backend.\n\nKEY RULES FOR AI AGENTS:\n• Two canvas views — ABox (individuals) and TBox (classes/properties). addNode writes to the store; the canvas shows the node in the correct view automatically. Use setViewMode to switch.\n• Ontology discovery first: loadOntology(query=\"use case\") → loadOntology(url=\"\") × N → setNamespace × N. foaf: is pre-loaded. OWL/RDFS/RDF/XSD always available.\n• Prefer domain ontologies over schema.org: foaf:Person, ical:Vevent, org:Organization, bot:Building.\n• For 5+ individuals use loadRdf(turtle=...) instead of N×addNode — one round-trip.\n• addLink blank-node limit: use loadRdf for owl:someValuesFrom/equivalentClass restrictions.\n• Batch up to 5 non-dependent calls per relay message. Send discovery (getNodes, queryGraph) alone.\n• Tool failed? Call help({tool:\"\"}) for the exact parameter schema.\n\nGRAPH ARCHITECTURE\nurn:vg:data = asserted triples (all mutations go here)\nurn:vg:inferred = OWL-RL derived (runReasoning writes; clearInferred removes)\nurn:vg:shapes = SHACL constraints (loadShacl writes; validateGraph reads)\n\nPre-loaded prefixes: rdf: rdfs: owl: xsd: foaf: skos: dc: dcterms: schema: ex:\nAfter loadOntology or setNamespace, those prefixes work in all tool IRI arguments.\n\nRecommended workflow:\nloadOntology(query=…) → loadOntology(url=\"\") × N → setNamespace × N\n→ setViewMode(\"tbox\") → addNode × N (owl:Class) → addLink × N → runLayout\n→ setViewMode(\"abox\") → loadRdf(turtle=...) OR addNode × N → addLink × N → runLayout\n→ runReasoning → fitCanvas + exportImage(svg) [last three safe to batch]\n\nAgent integration: (1) Claude Code / Playwright — window.__mcpTools[name](params) via browser_evaluate. (2) AI Relay Bridge — any AI chat controls Ontosphere via bookmarklet relay; see docs/relay-bridge.md and AGENTS.md.", + "description": "Browser-based RDF/ontology knowledge graph editor. ABox authoring, OWL-RL reasoning, layout, and export. All client-side, no backend.\n\nKEY RULES FOR AI AGENTS:\n• Two canvas views — ABox (individuals) and TBox (classes/properties). addNode writes to the store; the canvas shows the node in the correct view automatically. Use setViewMode to switch.\n• Ontology discovery first: loadOntology(query=\"use case\") → loadOntology(url=\"\") × N → setNamespace × N. foaf: is pre-loaded. OWL/RDFS/RDF/XSD always available.\n• Prefer domain ontologies over schema.org: foaf:Person, ical:Vevent, org:Organization, bot:Building.\n• For 5+ individuals use loadRdf(turtle=...) instead of N×addNode — one round-trip.\n• addTriple blank-node limit: use loadRdf for owl:someValuesFrom/equivalentClass restrictions.\n• Batch up to 5 non-dependent calls per relay message. Send discovery (getNodes, queryGraph) alone.\n• Tool failed? Call help({tool:\"\"}) for the exact parameter schema.\n\nGRAPH ARCHITECTURE\nurn:vg:data = asserted triples (all mutations go here)\nurn:vg:inferred = OWL-RL derived (runReasoning writes; clearInferred removes)\nurn:vg:shapes = SHACL constraints (loadShacl writes; validateGraph reads)\n\nPre-loaded prefixes: rdf: rdfs: owl: xsd: foaf: skos: dc: dcterms: schema: ex:\nAfter loadOntology or setNamespace, those prefixes work in all tool IRI arguments.\n\nRecommended workflow:\nloadOntology(query=…) → loadOntology(url=\"\") × N → setNamespace × N\n→ setViewMode(\"tbox\") → addNode × N (owl:Class) → addTriple × N → runLayout\n→ setViewMode(\"abox\") → loadRdf(turtle=...) OR addNode × N → addTriple × N → runLayout\n→ runReasoning → fitCanvas + exportImage(svg) [last three safe to batch]\n\nAgent integration: (1) Claude Code / Playwright — window.__mcpTools[name](params) via browser_evaluate. (2) AI Relay Bridge — any AI chat controls Ontosphere via bookmarklet relay; see docs/relay-bridge.md and AGENTS.md.", "tools": [ { "name": "loadRdf", @@ -148,7 +148,7 @@ }, { "name": "addNode", - "description": "Create a node (RDF subject) on the canvas by writing triples to the store. The node appears in the view that matches its rdf:type — owl:Class/owl:ObjectProperty etc. → TBox view; owl:NamedIndividual or unrecognised types → ABox view. Switch to the correct view with setViewMode BEFORE calling addNode so the node materialises immediately. For 5+ individuals use loadRdf(turtle=...) instead.", + "description": "Create a NEW entity on the canvas. Writes rdf:type + rdfs:label atomically so the canvas pipeline fires once, then auto-navigates to the new node. The node appears in the view matching its rdf:type (owl:Class → TBox; owl:ObjectProperty → TBox; owl:AnnotationProperty → TBox; owl:NamedIndividual or unknown → ABox). Object and annotation properties are first-class OWL entities — create them as nodes with typeIri=owl:ObjectProperty, then assert rdfs:domain and rdfs:range on them via addTriple. Use setViewMode before calling addNode. For 5+ individuals use loadRdf(turtle=...) instead. Use addTriple (not addNode) to add properties or relationships to an entity that already exists.", "inputSchema": { "type": "object", "properties": { @@ -203,7 +203,7 @@ }, { "name": "getNodes", - "description": "Discover or filter all known entities (TBox classes and ABox individuals). Use labelContains to find by name — falls back to fuzzy match if nothing found (response includes fuzzyFallback:true). Use typeIri to filter by class, focusFirst:true to pan the viewport to the first canvas match. Use this before addNode/addLink to resolve IRIs.", + "description": "Discover or filter all known entities (TBox classes and ABox individuals). Use labelContains to find by name — falls back to fuzzy match if nothing found (response includes fuzzyFallback:true). Use typeIri to filter by class, focusFirst:true to pan the viewport to the first canvas match. Use this before addNode/addTriple to resolve IRIs.", "inputSchema": { "type": "object", "properties": { @@ -225,8 +225,8 @@ } }, { - "name": "addLink", - "description": "Add a directed object-property triple (edge) between two canvas nodes: subjectIri --predicateIri--> objectIri. The edge appears on canvas immediately. Both subject and object must already be canvas nodes. BLANK NODE LIMITATION: addLink only connects named (IRI) nodes. OWL axioms that require blank nodes — owl:someValuesFrom, owl:allValuesFrom, owl:hasValue, owl:onProperty restrictions, owl:intersectionOf, owl:unionOf, owl:equivalentClass with anonymous restrictions — CANNOT be added with addLink. Use loadRdf with inline Turtle instead (see loadRdf description).", + "name": "addTriple", + "description": "Assert any RDF triple on entities that already exist on the canvas. IRI object → object-property edge, renders on canvas immediately (both nodes must already exist — call addNode first if needed). Literal object → annotation property (rdfs:comment, skos:definition, etc.), visible after expandNode. Use addNode (not addTriple) when creating an entity for the first time — addNode writes type+label atomically and navigates to the new node. BLANK NODE LIMITATION: addTriple cannot express OWL axioms requiring blank nodes (owl:someValuesFrom, owl:allValuesFrom, owl:hasValue restrictions, owl:intersectionOf, owl:unionOf). Use loadRdf with inline Turtle instead.", "inputSchema": { "type": "object", "properties": { @@ -235,7 +235,7 @@ }, "predicateIri": { "type": "string", - "description": "IRI of the object property (e.g. foaf:knows)" + "description": "IRI of the predicate (e.g. rdfs:subClassOf, owl:disjointWith, rdfs:domain)" }, "objectIri": { "type": "string" @@ -422,7 +422,7 @@ }, { "name": "getNodeDetails", - "description": "Fetch every asserted triple for one entity (from urn:vg:data only, not inferred). Use when you need property values, not just the IRI — call getNodes first to find the IRI.", + "description": "Fetch every triple for one entity — asserted (urn:vg:data) and inferred (urn:vg:inferred). Inferred triples are marked inferred:true. Use after runReasoning to inspect what was derived. Call getNodes first to find the IRI.", "inputSchema": { "type": "object", "required": [ diff --git a/public/demo-stage-owui.html b/public/demo-stage-owui.html new file mode 100644 index 00000000..31585828 --- /dev/null +++ b/public/demo-stage-owui.html @@ -0,0 +1,39 @@ + + + + + Ontosphere × OpenWebUI Demo Stage + + + +
+
+
OpenWebUI
+ +
+
+
Ontosphere
+ +
+
+ + + diff --git a/public/relay-bookmarklet.js b/public/relay-bookmarklet.js index 33934964..e7840e48 100644 --- a/public/relay-bookmarklet.js +++ b/public/relay-bookmarklet.js @@ -34,6 +34,19 @@ window.__vgRelayWatcher = null; } + /* ── Streaming detector — mutation-rate based, no UI-specific selectors ── */ + // Records timestamp of last DOM mutation. isAiStreaming() uses this as a + // generic fallback: if the page mutated recently, generation is likely active. + var STREAM_QUIET_MS = 4000; // ms of DOM silence before we consider generation done + window.__vgLastMutation = window.__vgLastMutation || 0; + var streamObserver = new MutationObserver(function () { + window.__vgLastMutation = Date.now(); + }); + streamObserver.observe(document.body, { childList: true, subtree: true, characterData: true }); + // Re-register under the relay observer slot so it's killed on next inject. + if (window.__vgStreamObserver) { try { window.__vgStreamObserver.disconnect(); } catch (_) {} } + window.__vgStreamObserver = streamObserver; + /* ── Instance ID — deactivates old message listeners ──────────────────── */ // Every click stamps a new ID. The message listener checks at runtime and // ignores messages if a newer instance has taken over. @@ -172,6 +185,50 @@ }); } + // Returns page text scoped to AI/assistant messages only where detectable, + // to avoid dispatching tool-call examples from user messages (e.g. starter prompt). + // Falls back to full page text (excluding input) for UIs without role markers. + function getAssistantText() { + // OWUI: data-message-author-role="assistant" on each message container + var nodes = document.querySelectorAll('[data-message-author-role="assistant"]'); + if (!nodes.length) nodes = document.querySelectorAll('[data-role="assistant"]'); + // FhGenie / generic: look for direct children of the message list that + // are NOT the user's own bubbles (right-aligned). Use aria role if present. + if (!nodes.length) nodes = document.querySelectorAll('[role="log"] [aria-label*="assistant" i], [role="log"] [aria-label*="bot" i]'); + if (nodes.length) { + var parts = []; + for (var i = 0; i < nodes.length; i++) parts.push(nodes[i].innerText || nodes[i].textContent || ''); + return parts.join('\n'); + } + return null; // no assistant-scoped nodes found + } + + // Returns page text excluding the chat input element. + // Prefers assistant-message-scoped text to avoid user-message tool-call examples. + // Textarea value is never in body.innerText so only contenteditable needs DOM exclusion. + // Uses SHOW_ELEMENT|SHOW_TEXT so FILTER_REJECT on the input element skips its whole subtree. + function getPageText() { + var assistantText = getAssistantText(); + if (assistantText !== null) return assistantText; + var inp = findInput(); + if (!inp || inp.tagName === 'TEXTAREA') + return document.body.innerText || document.body.textContent || ''; + var parts = []; + var walker = document.createTreeWalker( + document.body, + NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, + { acceptNode: function (node) { + if (node === inp) return NodeFilter.FILTER_REJECT; + if (node.nodeType === Node.TEXT_NODE) return NodeFilter.FILTER_ACCEPT; + return NodeFilter.FILTER_SKIP; + } + } + ); + var node; + while ((node = walker.nextNode())) parts.push(node.nodeValue); + return parts.join(' '); + } + /* ── Submit the chat input ─────────────────────────────────────────────── */ function submitInput(inputEl) { var foundBtn = false; @@ -188,7 +245,9 @@ var sendBtn = btns.find(function (b) { if (b.disabled) return false; var lbl = (b.getAttribute('aria-label') || b.title || b.textContent || '').toLowerCase(); - return b.type === 'submit' || lbl.includes('send') || lbl.includes('senden') || lbl.includes('submit'); + var cls = (b.className || '').toLowerCase(); + return b.type === 'submit' || lbl.includes('send') || lbl.includes('senden') || + lbl.includes('submit') || cls.includes('send') || cls.includes('submit'); }); if (sendBtn) { sendBtn.click(); foundBtn = true; break; } cur = cur.parentElement; @@ -208,7 +267,9 @@ } } - // 4. Enter keydown — textarea UIs only (TipTap: Enter = new paragraph) + // 4. Enter keydown — textarea UIs only, or when no button found. + // TipTap (OWUI) maps Enter → new paragraph, NOT submit. Button click is + // the correct submit path. Only fall back to Enter for non-TipTap UIs. if (inputEl.tagName === 'TEXTAREA' || !foundBtn) { ['keydown', 'keyup'].forEach(function (type) { inputEl.dispatchEvent(new KeyboardEvent(type, { @@ -232,149 +293,64 @@ return false; } - // Wait until the send button is enabled (Svelte/TipTap state flushed) then submit. - // Poll up to 3 s in 50 ms steps; fall through to requestSubmit() on timeout. - function doSubmit() { - var deadline = Date.now() + 3000; - (function poll() { - var directBtn = document.getElementById('send-message-button'); - var btnEnabled = directBtn && !directBtn.disabled; - if (!btnEnabled) { - // Also check tree-climbed send button - var cur = el.parentElement; - while (cur && cur !== document.body && !btnEnabled) { - var found = Array.from(cur.querySelectorAll('button')).find(function (b) { - if (b.disabled) return false; - var lbl = (b.getAttribute('aria-label') || b.title || b.textContent || '').toLowerCase(); - return b.type === 'submit' || lbl.includes('send') || lbl.includes('senden') || lbl.includes('submit'); - }); - if (found) btnEnabled = true; - cur = cur.parentElement; - } - } - if (btnEnabled || Date.now() >= deadline) { - submitInput(el); - injectInProgress = false; - } else { - setTimeout(poll, 50); - } - })(); - } - if (el.tagName === 'TEXTAREA') { + // Textarea: set value via native setter + input event, then defer submit one + // render cycle so React/framework state updates before the button is clicked. el.focus(); var setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set; setter.call(el, (el.value ? el.value + '\n' : '') + text); el.dispatchEvent(new Event('input', { bubbles: true })); - doSubmit(); + setTimeout(function () { submitInput(el); injectInProgress = false; }, 50); } else { - // ProseMirror / TipTap contenteditable (OpenWebUI, ChatGPT…) - // - // Three-path strategy, most reliable first: - // - // Path 1: Direct ProseMirror view dispatch via element.pmViewDesc.view. - // TipTap attaches the ProseMirror EditorView to the DOM element. - // Dispatching a transaction through it updates both PM state and Svelte - // store atomically — no DOM reconciliation, no race conditions. - // - // Path 2: Synthetic beforeinput(insertFromPaste) with DataTransfer. - // ProseMirror handles this through its beforeinput handler, reading - // event.dataTransfer. Falls back to this if Path 1 isn't available. - // - // Path 3: beforeinput(insertText) with data field — last resort. - // - // Focus first, defer 50 ms so ProseMirror has an active selection. + // TipTap/ProseMirror contenteditable (OWUI). + // Wait until the UI is ready (isAiStreaming()=false) before calling setContent — + // injecting while OWUI is still transitioning out of streaming state causes a race + // where the paste lands before the editor accepts input. Signal 4 (DOM mutation + // rate) was removed so this check no longer deadlocks on content injection. var live = findInput() || el; live.focus(); el = live; + var injectDeadline = Date.now() + 10000; - setTimeout(function () { + (function waitReady() { + if (isAiStreaming() && Date.now() < injectDeadline) { + setTimeout(waitReady, 100); + return; + } var target = findInput() || el; - target.focus(); - var dispatched = false; - - // ── Path 1: TipTap / ProseMirror dispatch ─────────────────────── - // Strategies (most reliable first): - // a) tiptap.commands.setContent — full TipTap pipeline, fires onTransaction - // → onChange → Svelte prompt update → send button enables - // b) pmView.dispatch (raw PM transaction) — fallback for older TipTap - // c) own-property scan for EditorView-shaped objects (minified builds) - try { - var tiptap = target.editor; - - // (a) TipTap high-level API — preferred; goes through full callback chain - if (tiptap && tiptap.commands && typeof tiptap.commands.setContent === 'function') { - try { - tiptap.commands.focus(); - // setContent(content, emitUpdate) — emitUpdate=true fires onTransaction - tiptap.commands.setContent(text, true); - dispatched = (tiptap.state || tiptap.view.state).doc.textContent.length > 0; - } catch (_) {} - } + var tiptap = target.editor; - // (b) raw PM dispatch — works when tiptap.commands unavailable - if (!dispatched) { - var pmView = null; - if (tiptap && tiptap.view && typeof tiptap.view.dispatch === 'function') { - pmView = tiptap.view; - } - if (!pmView) { - var desc = target.pmViewDesc; - if (desc && desc.view && typeof desc.view.dispatch === 'function') pmView = desc.view; - } - if (!pmView) { - var ownKeys = Object.keys(target); - for (var oi = 0; oi < ownKeys.length; oi++) { - try { - var ov = target[ownKeys[oi]]; - if (ov && typeof ov === 'object' && typeof ov.dispatch === 'function' && - ov.state && typeof ov.state.tr === 'object') { pmView = ov; break; } - } catch (_) {} - } - } - if (pmView) { - var state = pmView.state; - var docSize = state.doc.content.size; - var endPos = docSize > 2 ? docSize - 1 : 1; - var pmTr = state.tr.insertText(text, 1, endPos); - pmView.dispatch(pmTr); - dispatched = pmView.state.doc.textContent.length > 0; - } - } - } catch (_) {} - - // ── Path 2: beforeinput insertFromPaste with DataTransfer ─────── - if (!dispatched) { - try { - var dt = new DataTransfer(); - dt.setData('text/plain', text); - target.dispatchEvent(new InputEvent('beforeinput', { - inputType: 'insertFromPaste', - dataTransfer: dt, - bubbles: true, - cancelable: true, - })); - dispatched = true; - } catch (_) {} + if (!tiptap || !tiptap.commands || typeof tiptap.commands.setContent !== 'function') { + injectInProgress = false; + return; } - // ── Path 3: beforeinput insertText ────────────────────────────── - if (!dispatched) { - try { - target.dispatchEvent(new InputEvent('beforeinput', { - inputType: 'insertText', - data: text, - bubbles: true, - cancelable: true, - })); - } catch (_) {} - } + tiptap.commands.focus(); + tiptap.commands.setContent(text, true); + var submitDeadline = Date.now() + 10000; + + // Try to submit, then check whether the editor was cleared. + // A cleared editor = OWUI accepted the submit. Retry every 300ms until + // it works or deadline. Only click when isAiStreaming() is false so we + // don't submit into a mid-think or post-stream annotation window. + // Initial 600ms grace period lets OWUI finish its post-stream annotation + // phase — no generic DOM/network signal reliably marks the end of it. + setTimeout(function trySubmit() { + var inp = findInput() || target; + var tp = inp.editor || tiptap; + var isEmpty = tp.isEmpty !== undefined ? tp.isEmpty + : !(tp.getText ? tp.getText().trim() : (inp.innerText || inp.textContent || '').trim()); + if (isEmpty) { injectInProgress = false; return; } + + if (!isAiStreaming()) { + var btn = findSendButton(inp); + if (btn && !btn.disabled) { el = inp; btn.click(); } + } - // Belt-and-suspenders: fire input so Svelte bindings notice any change - target.dispatchEvent(new Event('input', { bubbles: true })); - el = target; - doSubmit(); - }, 50); + if (Date.now() >= submitDeadline) { injectInProgress = false; return; } + setTimeout(trySubmit, 300); + }, 600); + })(); } return true; @@ -607,22 +583,88 @@ } /* ── Streaming-idle detection ──────────────────────────────────────────── */ + // Primary signal: find the send/submit button and read its state. + // A visible, enabled send button (not showing a stop icon) = AI is idle. + // Absent, disabled-with-content, or replaced by a stop icon = generating. + // Fallback signals cover UIs where no send button is discoverable. + function findSendButton(inp) { + var direct = document.getElementById('send-message-button'); + if (direct) return direct; + var cur = inp && inp.parentElement; + while (cur && cur !== document.body) { + var found = null; + var candidates = cur.querySelectorAll('button'); + for (var ci = 0; ci < candidates.length; ci++) { + var cb = candidates[ci]; + var lbl = (cb.getAttribute('aria-label') || cb.title || cb.textContent || '').toLowerCase(); + var cls = (cb.className || '').toLowerCase(); + if (cb.type === 'submit' || lbl.includes('send') || lbl.includes('senden') || + lbl.includes('submit') || cls.includes('send') || cls.includes('submit')) { + found = cb; break; + } + } + if (found) return found; + cur = cur.parentElement; + } + return null; + } + function isAiStreaming() { var inp = findInput(); + var sendBtn = findSendButton(inp); + + if (sendBtn) { + // Stop icon in button (FhGenie: same button, SVG swaps arrow→X during generation) + var xPath = sendBtn.querySelector('path[d^="M8.22 8.22"]'); + if (xPath && sendBtn.offsetWidth > 0) return true; + + // Enabled = idle; disabled + has content = generating (OWUI pattern). + // Disabled + empty = idle (FhGenie: send button disabled when input empty). + // OWUI during generation: send button is replaced by a stop button, so + // findSendButton() returns null and the fallback signals below handle it. + if (!sendBtn.disabled) return false; + if (inp) { + var content = inp.tagName === 'TEXTAREA' + ? inp.value + : (inp.innerText || inp.textContent || ''); + if (content.trim().length > 0) return true; + } + return false; + } + + // Fallback for UIs without a discoverable send button if (inp) { - // Textarea-specific: disabled during generation if (inp.tagName === 'TEXTAREA' && inp.disabled) return true; if (inp.getAttribute('aria-disabled') === 'true') return true; - // aria-busy on any ancestor signals generation in progress var el = inp.parentElement; while (el && el !== document.body) { if (el.getAttribute('aria-busy') === 'true') return true; el = el.parentElement; } - // NOTE: we intentionally do NOT check the send button's disabled state here. - // TipTap/ProseMirror UIs disable the send button when the editor is empty, - // which is the normal idle state — not a streaming indicator. } + + var SPINNER_SEL = '[class*="spinner" i],[class*="loading" i],[class*="typing" i],' + + '[class*="thinking" i],[class*="generating" i],[class*="streaming" i],' + + '[class*="skeleton" i]'; + var spinnerEls = document.querySelectorAll(SPINNER_SEL); + for (var si = 0; si < spinnerEls.length; si++) { + var sp = spinnerEls[si]; + if (inp && inp.contains(sp)) continue; + if (sp.offsetWidth > 0 || sp.offsetHeight > 0) return true; + } + + var STOP_WORDS = ['stop', 'stopp', 'cancel', 'abort', 'halt', 'abbrechen', 'arrêter', 'interrompi']; + var btns = document.querySelectorAll('button:not([disabled])'); + for (var bi = 0; bi < btns.length; bi++) { + var b = btns[bi]; + if (b.offsetWidth === 0 && b.offsetHeight === 0) continue; + var lbl2 = (b.getAttribute('aria-label') || b.title || '').toLowerCase(); + var txt = b.textContent.trim().toLowerCase(); + for (var wi = 0; wi < STOP_WORDS.length; wi++) { + if (lbl2.indexOf(STOP_WORDS[wi]) !== -1 || txt === STOP_WORDS[wi]) return true; + } + } + return false; } @@ -679,54 +721,37 @@ }, 200); } - /* ── MutationObserver ──────────────────────────────────────────────────── */ - var debounceTimer = null; - var pendingNodes = new Set(); + /* ── Fire-on-idle poll ─────────────────────────────────────────────────── */ + // Polls every 500 ms. getPageText() excludes input field content so typed + // or pasted text never triggers dispatch. + // Requires 2 consecutive idle ticks (1 s) before extracting tool calls. + // This prevents user-message examples from being dispatched in the brief + // window between user submit and the model starting to stream. + var idlePollTimer = null; + var idleConsecutive = 0; - function flushPending() { - pendingNodes.forEach(function (node) { parseAndEnqueue(node); }); - pendingNodes.clear(); - } - - function collectAncestors(node) { - var el = node.nodeType === 3 ? node.parentElement : node; - // Ignore mutations inside editable elements (user typing / our inject) - var check = el; - while (check && check !== document.body) { - if (check.tagName === 'TEXTAREA' || check.contentEditable === 'true') return; - check = check.parentElement; - } - // Stop at the NEAREST block element — do not climb to large chat-stream - // containers that also contain user messages with example tool calls. - // MCP tool calls are always in a single block; the nearest p/code/div - // is sufficient to find them. - while (el && el !== document.body) { - var tag = el.tagName ? el.tagName.toLowerCase() : ''; - if (tag === 'p' || tag === 'li' || tag === 'pre' || tag === 'code' || - tag === 'div' || tag === 'section' || tag === 'article') { - pendingNodes.add(el); - return; + function idlePoll() { + if (window.__vgRelayInstanceId !== instanceId) return; // stale instance + if (!isAiStreaming() && !isProcessing && callQueue.length === 0) { + idleConsecutive++; + if (idleConsecutive >= 2) { + var calls = extractAllToolCalls(getPageText(), dispatchedSigs); + if (calls.length > 0) { + idleConsecutive = 0; + callQueue = callQueue.concat(calls); + processNextInQueue(); + } } - el = el.parentElement; + } else { + idleConsecutive = 0; } - if (el === document.body) pendingNodes.add(document.body); + idlePollTimer = setTimeout(idlePoll, 500); } - var observer = new MutationObserver(function (mutations) { - mutations.forEach(function (m) { - m.addedNodes.forEach(function (n) { collectAncestors(n); }); - if (m.type === 'characterData') collectAncestors(m.target); - }); - clearTimeout(debounceTimer); - debounceTimer = setTimeout(flushPending, DEBOUNCE_MS); - }); - - // Pre-seed dispatchedSigs with all tool calls already visible on the page - // BEFORE starting the observer, so newly-arriving AI messages are not seeded. - // This prevents system-prompt examples (id:0 etc.) from being dispatched. - extractAllToolCalls(document.body.innerText || document.body.textContent || '', dispatchedSigs); + // Pre-seed: mark all calls currently visible on the page as already dispatched. + extractAllToolCalls(getPageText(), dispatchedSigs); - observer.observe(document.body, { childList: true, subtree: true, characterData: true }); - window.__vgRelayObserver = observer; + idlePoll(); + window.__vgRelayObserver = { disconnect: function () { clearTimeout(idlePollTimer); idlePollTimer = null; } }; })(); diff --git a/scripts/collect-demo-videos.mjs b/scripts/collect-demo-videos.mjs index 01e0f9f8..a7c04dd3 100644 --- a/scripts/collect-demo-videos.mjs +++ b/scripts/collect-demo-videos.mjs @@ -30,11 +30,26 @@ if (!fs.existsSync(RESULTS_DIR)) { process.exit(1); } +// Sort longest-first so pizza-tutorial-chat is claimed before pizza-tutorial +specNames.sort((a, b) => b.length - a.length); + +// Pre-assign each result subdir to its longest-matching spec name +const claimedDirs = new Map(); // subdir → name +for (const subdir of fs.readdirSync(RESULTS_DIR)) { + for (const name of specNames) { + if (subdir.startsWith(`demo-${name}`) && !claimedDirs.has(subdir)) { + claimedDirs.set(subdir, name); + break; + } + } +} + let copied = 0; for (const name of specNames) { // Playwright names the output dir: e2e-demo---demo/ - const subdirs = fs.readdirSync(RESULTS_DIR) - .filter(d => d.startsWith(`demo-${name}`)); + const subdirs = [...claimedDirs.entries()] + .filter(([, n]) => n === name) + .map(([d]) => d); for (const subdir of subdirs) { const videoPath = path.join(RESULTS_DIR, subdir, 'video.webm'); @@ -44,24 +59,43 @@ for (const name of specNames) { const size = (fs.statSync(dest).size / 1024).toFixed(0); console.log(`✓ docs/demo-videos/${name}.webm (${size} KB)`); - // Convert to mp4 for broad playback compatibility + // Shorten idle blocks and produce mp4 in one step. + // Samples at 0.5fps so cursor blinks/CSS animations don't break detection. + // Only blocks longer than 8s (viewer ingest wait) are shortened to 10s. const mp4 = path.join(OUTPUT_DIR, `${name}.mp4`); + let mp4ok = false; try { - execFileSync('ffmpeg', [ - '-y', '-i', dest, - '-c:v', 'libx264', - '-crf', '20', - '-preset', 'slow', - '-profile:v', 'high', - '-level:v', '4.2', - '-pix_fmt', 'yuv420p', - '-movflags', '+faststart', - mp4, - ], { stdio: 'pipe' }); + execFileSync('python3', [ + path.join(__dirname, 'shorten-idle.py'), + dest, // input webm + mp4, // output mp4 directly — libx264, no intermediate webm + '10.0', // digest seconds (keep 10s of each idle block) + '8.0', // min idle duration to shorten + '0.5', // sample fps (1 frame / 2s — ignores sub-2s animations) + '-30', // noise floor dB + ], { stdio: 'inherit' }); const mp4size = (fs.statSync(mp4).size / 1024).toFixed(0); console.log(`✓ docs/demo-videos/${name}.mp4 (${mp4size} KB)`); + mp4ok = true; } catch { - console.warn(` ffmpeg not found — skipping mp4 conversion for ${name}`); + console.warn(` shorten-idle.py failed — falling back to plain mp4 conversion`); + } + + // Fallback: plain webm → mp4 if shorten-idle failed + if (!mp4ok) { + try { + execFileSync('ffmpeg', [ + '-y', '-i', dest, + '-c:v', 'libx264', '-crf', '20', '-preset', 'slow', + '-profile:v', 'high', '-level:v', '4.2', + '-pix_fmt', 'yuv420p', '-movflags', '+faststart', + mp4, + ], { stdio: 'pipe' }); + const mp4size = (fs.statSync(mp4).size / 1024).toFixed(0); + console.log(`✓ docs/demo-videos/${name}.mp4 (${mp4size} KB) [no idle-trim]`); + } catch { + console.warn(` ffmpeg not found — skipping mp4 conversion for ${name}`); + } } copied++; diff --git a/scripts/debug-owui-controls.mjs b/scripts/debug-owui-controls.mjs new file mode 100644 index 00000000..6bafddd1 --- /dev/null +++ b/scripts/debug-owui-controls.mjs @@ -0,0 +1,91 @@ +/** + * Quick debug: open OWUI, navigate to new chat, click Controls, screenshot. + * Reads auth state from .playwright/owui-auth.json + */ +import { chromium } from 'playwright'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const OWUI_URL = process.env.OWUI_URL || 'https://gpuserver1-sit.iwm.fraunhofer.de'; +const AUTH_FILE = path.resolve(__dirname, '../.playwright/owui-auth.json'); +const OUT_DIR = path.resolve(__dirname, '../.playwright/debug-screenshots'); +fs.mkdirSync(OUT_DIR, { recursive: true }); + +async function shot(page, name) { + const p = path.join(OUT_DIR, `${name}.png`); + await page.screenshot({ path: p, fullPage: false }); + console.log(` 📸 ${name} → ${p}`); +} + +async function main() { + const browser = await chromium.launch({ + headless: false, + args: ['--disable-web-security', '--ignore-certificate-errors'], + }); + const state = JSON.parse(fs.readFileSync(AUTH_FILE, 'utf8')); + const context = await browser.newContext({ ignoreHTTPSErrors: true, storageState: state }); + const page = await context.newPage(); + + console.log('→ navigate to OWUI new chat…'); + await page.goto(`${OWUI_URL}/`); + await page.waitForTimeout(3000); + await shot(page, '01-home'); + + // Click Controls button + console.log('→ clicking Controls…'); + const controlsBtn = page.locator('button[aria-label*="Controls" i]').first(); + await controlsBtn.click(); + await page.waitForTimeout(1500); + await shot(page, '02-controls-open'); + + // Snapshot accessibility tree + const snap = await page.accessibility.snapshot(); + const snapPath = path.join(OUT_DIR, 'a11y-snapshot.json'); + fs.writeFileSync(snapPath, JSON.stringify(snap, null, 2)); + console.log(` 🌲 a11y snapshot → ${snapPath}`); + + // Inspect modal & buttons + const modalBtns = await page.locator('div.modal button').all(); + console.log(` modal buttons count: ${modalBtns.length}`); + for (let i = 0; i < Math.min(5, modalBtns.length); i++) { + const txt = await modalBtns[i].textContent(); + const lbl = await modalBtns[i].getAttribute('aria-label'); + console.log(` [${i}] text="${txt?.trim()}" aria-label="${lbl}"`); + } + + // Look for system prompt textarea + const sysField = page.locator([ + '#system-prompt-input', + 'textarea[placeholder*="system" i]', + 'textarea[id*="system" i]', + ].join(', ')).first(); + const sysCount = await sysField.count(); + console.log(` system prompt field found: ${sysCount > 0}`); + if (sysCount > 0) { + const tag = await sysField.evaluate(el => el.tagName.toLowerCase()); + const phld = await sysField.getAttribute('placeholder'); + console.log(` tag=${tag} placeholder="${phld}"`); + } + + // Fill system prompt + if (sysCount > 0) { + await sysField.click(); + await sysField.fill('DEBUG TEST'); + await page.waitForTimeout(500); + await shot(page, '03-prompt-filled'); + } + + // Try closing via first modal button (the ✕) + if (modalBtns.length > 0) { + console.log('→ clicking first modal button (✕ close)…'); + await modalBtns[0].click(); + await page.waitForTimeout(800); + await shot(page, '04-after-close'); + } + + await browser.close(); + console.log('Done. Check .playwright/debug-screenshots/'); +} +main().catch(e => { console.error(e); process.exit(1); }); diff --git a/scripts/owui-auth.mjs b/scripts/owui-auth.mjs new file mode 100644 index 00000000..91290938 --- /dev/null +++ b/scripts/owui-auth.mjs @@ -0,0 +1,53 @@ +/** + * Login to OpenWebUI and save Playwright auth state. + * + * Credentials come exclusively from env vars — never from CLI args or files. + * The output file (.playwright/owui-auth.json) is gitignored. + * + * Usage: + * OWUI_URL=https://... OWUI_EMAIL=user@example.com OWUI_PASSWORD=secret \ + * node scripts/owui-auth.mjs + */ +import { chromium } from 'playwright'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const OWUI_URL = process.env.OWUI_URL; +const EMAIL = process.env.OWUI_EMAIL; +const PASSWORD = process.env.OWUI_PASSWORD; + +if (!OWUI_URL || !EMAIL || !PASSWORD) { + console.error('Missing env vars: OWUI_URL, OWUI_EMAIL, OWUI_PASSWORD'); + process.exit(1); +} + +const OUT = path.resolve(__dirname, '../.playwright/owui-auth.json'); + +async function main() { + const browser = await chromium.launch({ + args: ['--ignore-certificate-errors'], + }); + const context = await browser.newContext({ ignoreHTTPSErrors: true }); + const page = await context.newPage(); + + console.log(`Logging in to ${OWUI_URL} …`); + await page.goto(`${OWUI_URL}/auth`); + + await page.fill('input[placeholder*="Email"], input[type="email"]', EMAIL); + await page.fill('input[type="password"]', PASSWORD); + await page.click('button:has-text("Sign in")'); + await page.waitForURL(`${OWUI_URL}/`, { timeout: 20_000 }); + + console.log('Login OK. Saving auth state…'); + const state = await context.storageState(); + fs.mkdirSync(path.dirname(OUT), { recursive: true }); + fs.writeFileSync(OUT, JSON.stringify(state, null, 2)); + console.log(`Saved → ${OUT}`); + + await browser.close(); +} + +main().catch(e => { console.error(e); process.exit(1); }); diff --git a/scripts/shorten-idle.py b/scripts/shorten-idle.py new file mode 100644 index 00000000..9863b8a3 --- /dev/null +++ b/scripts/shorten-idle.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +""" +shorten-idle.py — Shorten frozen/static sections in a demo video. + +Downsamples to `sample_fps` before running freezedetect so that minor +browser animations (cursor blinks, CSS transitions) are invisible to the +detector. Only sections longer than `min_freeze_sec` are shortened — +i.e. only the big idle blocks that exceed a viewer's ingest wait time. + +Usage: + python3 scripts/shorten-idle.py [digest_sec] [min_freeze_sec] [sample_fps] [noise_db] + + input Path to source video (webm, mp4, …) + output Path for trimmed output (mp4 recommended) + digest_sec Seconds to keep of each idle section (default: 3.0) + min_freeze_sec Minimum idle duration to shorten (default: 8.0) + sample_fps Frames per second used for detection (default: 0.5) + 0.5 = one frame every 2 s — ignores sub-2s animations + noise_db Freeze noise floor in dB (default: -30) + Higher (e.g. -20) = more tolerant of minor pixel changes + +Example: + python3 scripts/shorten-idle.py docs/demo-videos/openwebui-socratic.webm \\ + docs/demo-videos/openwebui-socratic.mp4 +""" + +import re +import subprocess +import sys +from pathlib import Path + + +def detect_freezes( + path: Path, + noise_db: float, + min_dur: float, + sample_fps: float, +) -> list[tuple[float, float, float]]: + # Downsample to sample_fps so transient browser animations don't break detection. + # freezedetect timestamps are in the downsampled stream; they map 1-to-1 to real + # seconds because setpts=PTS is not applied — we only change the frame rate seen + # by the filter, not the pts values. + vf = f"fps={sample_fps},freezedetect=n={noise_db}dB:d={min_dur}" + cmd = ["ffmpeg", "-v", "info", "-i", str(path), "-vf", vf, "-f", "null", "-"] + result = subprocess.run(cmd, capture_output=True, text=True) + text = result.stderr + + starts, ends = [], [] + for line in text.splitlines(): + if "freeze_start:" in line: + m = re.search(r"freeze_start: ([\d.]+)", line) + if m: + starts.append(float(m.group(1))) + elif "freeze_end:" in line: + m = re.search(r"freeze_end: ([\d.]+)", line) + if m: + ends.append(float(m.group(1))) + + if len(starts) != len(ends): + print(f"Warning: mismatched freeze_start/end counts ({len(starts)}/{len(ends)}), truncating") + n = min(len(starts), len(ends)) + starts, ends = starts[:n], ends[:n] + + return [(s, e, e - s) for s, e in zip(starts, ends)] + + +def get_duration(path: Path) -> float: + cmd = [ + "ffprobe", "-v", "error", + "-show_entries", "format=duration", + "-of", "default=noprint_wrappers=1:nokey=1", + str(path), + ] + result = subprocess.run(cmd, capture_output=True, text=True) + return float(result.stdout.strip()) + + +def build_keep_segments( + freezes: list[tuple[float, float, float]], + total: float, + digest: float, +) -> list[tuple[float, float]]: + segments: list[tuple[float, float]] = [] + pos = 0.0 + for freeze_start, freeze_end, _ in sorted(freezes): + if freeze_start > pos: + segments.append((pos, freeze_start)) + keep_end = min(freeze_start + digest, freeze_end) + segments.append((freeze_start, keep_end)) + pos = freeze_end + if pos < total: + segments.append((pos, total)) + return [(s, e) for s, e in segments if e - s > 0.01] + + +def has_audio(path: Path) -> bool: + cmd = [ + "ffprobe", "-v", "error", + "-select_streams", "a", + "-show_entries", "stream=codec_type", + "-of", "default=noprint_wrappers=1", + str(path), + ] + result = subprocess.run(cmd, capture_output=True, text=True) + return bool(result.stdout.strip()) + + +def build_ffmpeg_cmd( + input_path: Path, + output_path: Path, + segments: list[tuple[float, float]], +) -> list[str]: + n = len(segments) + parts: list[str] = [] + audio = has_audio(input_path) + + # concat filter requires interleaved [v0][a0][v1][a1]..., not grouped + concat_inputs: list[str] = [] + for i, (start, end) in enumerate(segments): + parts.append(f"[0:v]trim=start={start:.3f}:end={end:.3f},setpts=PTS-STARTPTS[v{i}]") + concat_inputs.append(f"[v{i}]") + if audio: + parts.append(f"[0:a]atrim=start={start:.3f}:end={end:.3f},asetpts=PTS-STARTPTS[a{i}]") + concat_inputs.append(f"[a{i}]") + + if audio: + parts.append(f"{''.join(concat_inputs)}concat=n={n}:v=1:a=1[outv][outa]") + map_args = ["-map", "[outv]", "-map", "[outa]"] + audio_args = ["-c:a", "aac", "-b:a", "128k"] + else: + parts.append(f"{''.join(concat_inputs)}concat=n={n}:v=1:a=0[outv]") + map_args = ["-map", "[outv]"] + audio_args = [] + + ext = output_path.suffix.lower() + if ext == ".webm": + video_args = ["-c:v", "libvpx", "-b:v", "1M", "-deadline", "good", "-cpu-used", "4"] + if audio: + audio_args = ["-c:a", "libvorbis", "-b:a", "128k"] + else: + video_args = ["-c:v", "libx264", "-crf", "18", "-preset", "fast", + "-profile:v", "high", "-pix_fmt", "yuv420p", "-movflags", "+faststart"] + + return [ + "ffmpeg", "-y", + "-i", str(input_path), + "-filter_complex", "; ".join(parts), + *map_args, + *video_args, + *audio_args, + str(output_path), + ] + + +def main() -> None: + args = sys.argv[1:] + if len(args) < 2: + print(__doc__) + sys.exit(1) + + input_path = Path(args[0]) + output_path = Path(args[1]) + digest_sec = float(args[2]) if len(args) > 2 else 3.0 + min_freeze = float(args[3]) if len(args) > 3 else 8.0 + sample_fps = float(args[4]) if len(args) > 4 else 0.5 + noise_db = float(args[5]) if len(args) > 5 else -30.0 + + if not input_path.exists(): + print(f"Error: {input_path} not found") + sys.exit(1) + + print(f"Input: {input_path}") + total_dur = get_duration(input_path) + print(f"Duration: {total_dur:.1f}s") + print(f"Detecting idle blocks (sample={sample_fps}fps, noise={noise_db}dB, min={min_freeze}s) …") + + freezes = detect_freezes(input_path, noise_db, min_freeze, sample_fps) + + if not freezes: + print("No idle blocks detected — nothing to shorten.") + sys.exit(0) + + saved = 0.0 + print(f"\nFound {len(freezes)} idle block(s):") + for start, end, dur in freezes: + save = max(0.0, dur - digest_sec) + saved += save + print(f" {start:6.1f}s – {end:6.1f}s ({dur:.1f}s) shorten by {save:.1f}s") + + print(f"\nTime saved: {saved:.1f}s ({saved / total_dur * 100:.0f}%)") + print(f"Output duration: ~{total_dur - saved:.1f}s") + + segments = build_keep_segments(freezes, total_dur, digest_sec) + print(f"Segments to keep: {len(segments)}") + + cmd = build_ffmpeg_cmd(input_path, output_path, segments) + print(f"\nRunning ffmpeg …") + result = subprocess.run(cmd) + + if result.returncode == 0: + size_mb = output_path.stat().st_size / 1024 / 1024 + print(f"\nDone: {output_path} ({size_mb:.1f} MB)") + else: + print(f"\nffmpeg exited with code {result.returncode}") + sys.exit(result.returncode) + + +if __name__ == "__main__": + main() diff --git a/src/__tests__/stores/reasoning_named_restriction_collapse.test.ts b/src/__tests__/stores/reasoning_named_restriction_collapse.test.ts new file mode 100644 index 00000000..4aec4a78 --- /dev/null +++ b/src/__tests__/stores/reasoning_named_restriction_collapse.test.ts @@ -0,0 +1,353 @@ +// @vitest-environment node + +/** + * OWL restriction classification regression test. + * + * Verifies that two OWL restrictions sharing owl:onProperty but with DIFFERENT + * owl:someValuesFrom fillers do NOT collapse into an equivalentClass pair, and + * that ABox individuals are classified into exactly the classes whose restriction + * fillers they satisfy. + * + * Three variants: + * - Named restriction nodes (ex:R1, ex:R2) via applyBatch + * - Blank restriction nodes (_:b0, _:b1) via applyBatch — skolemized to urn:vg:bnode: in store + * - Blank restriction nodes via loadRDFIntoGraph (Turtle) — also skolemized + * + * All variants must produce identical correct results. + */ + +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { describe, it, expect, beforeEach, afterEach } from "vitest"; +import { initRdfManagerWorker } from "../utils/initRdfManagerWorker"; +import { rdfManager } from "../../utils/rdfManager"; +import { DataFactory } from "n3"; +import { RDF_TYPE } from "../../constants/vocabularies"; + +const { namedNode, blankNode, quad } = DataFactory; + +const DATA_GRAPH = "urn:vg:data"; +const INFERRED_GRAPH = "urn:vg:inferred"; +const EX = "http://example.org/collapse-test#"; + +const NS_OWL = "http://www.w3.org/2002/07/owl#"; +const RDF_TYPE_NODE = namedNode(RDF_TYPE); +const OWL_RESTRICTION = namedNode(`${NS_OWL}Restriction`); +const OWL_ON_PROPERTY = namedNode(`${NS_OWL}onProperty`); +const OWL_SOME_VALUES_FROM = namedNode(`${NS_OWL}someValuesFrom`); +const OWL_EQUIVALENT_CLASS = namedNode(`${NS_OWL}equivalentClass`); +const OWL_NAMED_INDIVIDUAL_NODE = namedNode(`${NS_OWL}NamedIndividual`); + +// --------------------------------------------------------------------------- +// Fetch shim — serve local rule files, pass everything else to real fetch +// --------------------------------------------------------------------------- +let origFetch: typeof globalThis.fetch; + +function installFetchShim() { + origFetch = (globalThis as any).fetch; + (globalThis as any).fetch = async (input: any, init?: any) => { + try { + const urlStr = String(input ?? ""); + if (urlStr.includes("/reasoning-rules/")) { + const name = urlStr.replace(/^.*\/reasoning-rules\//, ""); + const filePath = resolve("public/reasoning-rules", name); + const text = readFileSync(filePath, "utf8"); + return { + ok: true, + status: 200, + text: async () => text, + headers: { get: (k: string) => (k?.toLowerCase() === "content-type" ? "text/n3" : null) }, + }; + } + } catch (_) { + // fall through + } + if (typeof origFetch === "function") return origFetch(input, init); + throw new Error(`No fetch available for: ${input}`); + }; +} + +function uninstallFetchShim() { + (globalThis as any).fetch = origFetch; +} + +// --------------------------------------------------------------------------- +// Helper: fetch all inferred quads +// --------------------------------------------------------------------------- +async function getInferredQuads(): Promise> { + const page = await rdfManager.fetchQuadsPage({ + graphName: INFERRED_GRAPH, + offset: 0, + limit: 10000, + serialize: true, + }); + return (page?.items ?? []).map((q: any) => ({ + s: q.subject?.value ?? q.subject, + p: q.predicate?.value ?? q.predicate, + o: q.object?.value ?? q.object, + })); +} + +function inferredIncludes( + quads: Array<{ s: string; p: string; o: string }>, + s: string, + p: string, + o: string, +): boolean { + return quads.some((q) => q.s === s && q.p === p && q.o === o); +} + +// --------------------------------------------------------------------------- +// Shared TBox+ABox helpers +// --------------------------------------------------------------------------- + +async function seedNamedNodeGraph() { + const R1 = namedNode(`${EX}R1`); + const R2 = namedNode(`${EX}R2`); + const ClassA = namedNode(`${EX}ClassA`); + const ClassB = namedNode(`${EX}ClassB`); + const hasPart = namedNode(`${EX}hasPart`); + const FillerA = namedNode(`${EX}FillerA`); + const FillerB = namedNode(`${EX}FillerB`); + const ind1 = namedNode(`${EX}ind1`); + const p1 = namedNode(`${EX}p1`); + + await rdfManager.applyBatch( + { + adds: [ + // R1 = someValuesFrom FillerA + quad(R1, RDF_TYPE_NODE, OWL_RESTRICTION), + quad(R1, OWL_ON_PROPERTY, hasPart), + quad(R1, OWL_SOME_VALUES_FROM, FillerA), + // R2 = someValuesFrom FillerB — same property, different filler + quad(R2, RDF_TYPE_NODE, OWL_RESTRICTION), + quad(R2, OWL_ON_PROPERTY, hasPart), + quad(R2, OWL_SOME_VALUES_FROM, FillerB), + // ClassA ≡ R1, ClassB ≡ R2 + quad(ClassA, OWL_EQUIVALENT_CLASS, R1), + quad(ClassB, OWL_EQUIVALENT_CLASS, R2), + // ABox: ind1 hasPart p1; p1 type FillerA only + quad(ind1, RDF_TYPE_NODE, OWL_NAMED_INDIVIDUAL_NODE), + quad(ind1, hasPart, p1), + quad(p1, RDF_TYPE_NODE, FillerA), + ], + removes: [], + }, + DATA_GRAPH, + ); +} + +async function seedBlankNodeGraph() { + const b0 = blankNode("b0"); + const b1 = blankNode("b1"); + const ClassA = namedNode(`${EX}ClassA`); + const ClassB = namedNode(`${EX}ClassB`); + const hasPart = namedNode(`${EX}hasPart`); + const FillerA = namedNode(`${EX}FillerA`); + const FillerB = namedNode(`${EX}FillerB`); + const ind1 = namedNode(`${EX}ind1`); + const p1 = namedNode(`${EX}p1`); + + await rdfManager.applyBatch( + { + adds: [ + quad(b0, RDF_TYPE_NODE, OWL_RESTRICTION), + quad(b0, OWL_ON_PROPERTY, hasPart), + quad(b0, OWL_SOME_VALUES_FROM, FillerA), + quad(b1, RDF_TYPE_NODE, OWL_RESTRICTION), + quad(b1, OWL_ON_PROPERTY, hasPart), + quad(b1, OWL_SOME_VALUES_FROM, FillerB), + quad(ClassA, OWL_EQUIVALENT_CLASS, b0), + quad(ClassB, OWL_EQUIVALENT_CLASS, b1), + quad(ind1, RDF_TYPE_NODE, OWL_NAMED_INDIVIDUAL_NODE), + quad(ind1, hasPart, p1), + quad(p1, RDF_TYPE_NODE, FillerA), + ], + removes: [], + }, + DATA_GRAPH, + ); +} + +// --------------------------------------------------------------------------- +// Shared assertion logic +// --------------------------------------------------------------------------- +async function assertCorrectClassification(label: string) { + const quads = await getInferredQuads(); + + console.log(`[TEST] All inferred quads (${label}):`); + for (const q of quads) console.log(" ", q.s, q.p, q.o); + + const classA = `${EX}ClassA`; + const classB = `${EX}ClassB`; + const ind1 = `${EX}ind1`; + const equivClass = OWL_EQUIVALENT_CLASS.value; + const rdfType = RDF_TYPE; + + // No spurious ClassA ≡ ClassB equivalence + const spuriousEquiv = + inferredIncludes(quads, classA, equivClass, classB) || + inferredIncludes(quads, classB, equivClass, classA); + expect(spuriousEquiv).toBe(false); + + // ind1 correctly classified as ClassA via cls-svf1 + expect(inferredIncludes(quads, ind1, rdfType, classA)).toBe(true); + + // ind1 must NOT be classified as ClassB (p1 only satisfies FillerA, not FillerB) + expect(inferredIncludes(quads, ind1, rdfType, classB)).toBe(false); +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- +describe("OWL restriction classification — no collapse (regression guard)", () => { + beforeEach(async () => { + await initRdfManagerWorker(); + await rdfManager.clear(); + installFetchShim(); + }); + + afterEach(() => { + uninstallFetchShim(); + }); + + it("NAMED NODES — no collapse; ind1 typed as ClassA only", async () => { + await seedNamedNodeGraph(); + + const result = await rdfManager.runReasoning({ rulesets: ["owl-rl.n3"] }); + + if ((result.meta as any)?.ruleQuadCount === 0) { + console.warn("[TEST] No rule quads loaded — skipping"); + return; + } + + expect(result.status).toBe("completed"); + await assertCorrectClassification("named-node"); + }); + + it("BLANK NODES via applyBatch — skolemized to urn:vg:bnode:; no collapse; ind1 typed as ClassA only", async () => { + await seedBlankNodeGraph(); + + // Blank nodes are skolemized at write time — verify no _: subjects in store + const dataPage = await rdfManager.fetchQuadsPage({ graphName: DATA_GRAPH, limit: 10000 }); + const dataQuads = dataPage?.items ?? []; + const hasRawBnode = dataQuads.some((q: any) => (q.subject?.value ?? q.subject ?? "").startsWith("_:")); + expect(hasRawBnode).toBe(false); + const hasSkolem = dataQuads.some((q: any) => (q.subject?.value ?? q.subject ?? "").startsWith("urn:vg:bnode:")); + expect(hasSkolem).toBe(true); + + const result = await rdfManager.runReasoning({ rulesets: ["owl-rl.n3"] }); + + if ((result.meta as any)?.ruleQuadCount === 0) { + console.warn("[TEST] No rule quads loaded — skipping"); + return; + } + + expect(result.status).toBe("completed"); + await assertCorrectClassification("blank-node-batch"); + }); + + it("BLANK NODES via loadRDFIntoGraph (Turtle) — skolemized; no collapse; ind1 typed as ClassA only; export de-skolemizes", async () => { + const turtle = ` +@prefix ex: . +@prefix owl: . +@prefix rdf: . + +ex:ClassA owl:equivalentClass [ + rdf:type owl:Restriction ; + owl:onProperty ex:hasPart ; + owl:someValuesFrom ex:FillerA +] . + +ex:ClassB owl:equivalentClass [ + rdf:type owl:Restriction ; + owl:onProperty ex:hasPart ; + owl:someValuesFrom ex:FillerB +] . + +ex:ind1 rdf:type owl:NamedIndividual ; + ex:hasPart ex:p1 . + +ex:p1 rdf:type ex:FillerA . +`; + await rdfManager.loadRDFIntoGraph(turtle, DATA_GRAPH, "text/turtle"); + + // Blank nodes from Turtle parsing are skolemized — verify + const dataPage = await rdfManager.fetchQuadsPage({ graphName: DATA_GRAPH, limit: 10000 }); + const dataQuads = dataPage?.items ?? []; + const hasRawBnode = dataQuads.some((q: any) => (q.subject?.value ?? q.subject ?? "").startsWith("_:")); + expect(hasRawBnode).toBe(false); + const hasSkolem = dataQuads.some((q: any) => (q.subject?.value ?? q.subject ?? "").startsWith("urn:vg:bnode:")); + expect(hasSkolem).toBe(true); + + const result = await rdfManager.runReasoning({ rulesets: ["owl-rl.n3"] }); + + if ((result.meta as any)?.ruleQuadCount === 0) { + console.warn("[TEST] No rule quads loaded — skipping"); + return; + } + + expect(result.status).toBe("completed"); + await assertCorrectClassification("loadRdf-blank-node"); + + // Export round-trip: urn:vg:bnode: IRIs must be de-skolemized back to blank nodes + const exported = await rdfManager.exportToTurtle(DATA_GRAPH); + expect(exported).not.toContain("urn:vg:bnode:"); + expect(exported).toMatch(/_:[\w]+/); + }); + + it("BLANK NODES via individual addTriple calls — label-only skolemization; same label = same IRI; no collapse; ind1 typed as ClassA only", async () => { + // This simulates the MCP addTriple tool path: one triple per call. + // With label-only hashing, every call referencing "_:b0" produces the + // same urn:vg:bnode: IRI, so the restriction is built correctly. + const ClassA = `${EX}ClassA`; + const ClassB = `${EX}ClassB`; + const hasPart = `${EX}hasPart`; + const FillerA = `${EX}FillerA`; + const FillerB = `${EX}FillerB`; + const ind1 = `${EX}ind1`; + const p1 = `${EX}p1`; + const OWL = "http://www.w3.org/2002/07/owl#"; + const RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; + + // Restriction for ClassA via _:b0 — one addTriple call per triple + rdfManager.addTriple("_:b0", `${RDF}type`, `${OWL}Restriction`, DATA_GRAPH); + rdfManager.addTriple("_:b0", `${OWL}onProperty`, hasPart, DATA_GRAPH); + rdfManager.addTriple("_:b0", `${OWL}someValuesFrom`, FillerA, DATA_GRAPH); + rdfManager.addTriple(ClassA, `${OWL}equivalentClass`, "_:b0", DATA_GRAPH); + + // Restriction for ClassB via _:b1 — distinct label → distinct IRI + rdfManager.addTriple("_:b1", `${RDF}type`, `${OWL}Restriction`, DATA_GRAPH); + rdfManager.addTriple("_:b1", `${OWL}onProperty`, hasPart, DATA_GRAPH); + rdfManager.addTriple("_:b1", `${OWL}someValuesFrom`, FillerB, DATA_GRAPH); + rdfManager.addTriple(ClassB, `${OWL}equivalentClass`, "_:b1", DATA_GRAPH); + + // ABox + rdfManager.addTriple(ind1, `${RDF}type`, `${OWL}NamedIndividual`, DATA_GRAPH); + rdfManager.addTriple(ind1, hasPart, p1, DATA_GRAPH); + rdfManager.addTriple(p1, `${RDF}type`, FillerA, DATA_GRAPH); + + // Allow async worker writes to settle + await new Promise((r) => setTimeout(r, 200)); + + // Verify both blank nodes skolemized to distinct urn:vg:bnode: IRIs + const dataPage = await rdfManager.fetchQuadsPage({ graphName: DATA_GRAPH, limit: 10000 }); + const dataQuads = dataPage?.items ?? []; + const skolemSubjects = new Set( + dataQuads + .map((q: any) => q.subject?.value ?? q.subject ?? "") + .filter((s: string) => s.startsWith("urn:vg:bnode:")) + ); + expect(skolemSubjects.size).toBe(2); // _:b0 and _:b1 get distinct IRIs + + const result = await rdfManager.runReasoning({ rulesets: ["owl-rl.n3"] }); + + if ((result.meta as any)?.ruleQuadCount === 0) { + console.warn("[TEST] No rule quads loaded — skipping"); + return; + } + + expect(result.status).toBe("completed"); + await assertCorrectClassification("addTriple-blank-node"); + }); +}); diff --git a/src/components/Canvas/NodePropertyEditor.tsx b/src/components/Canvas/NodePropertyEditor.tsx index 97f643ef..d392502b 100644 --- a/src/components/Canvas/NodePropertyEditor.tsx +++ b/src/components/Canvas/NodePropertyEditor.tsx @@ -238,11 +238,11 @@ export const NodePropertyEditor = ({ : ""; setNodeIri(iri); - const rdfTypes: string[] = Array.isArray(sourceNode.rdfTypes) + const rdfTypes: string[] = (Array.isArray(sourceNode.rdfTypes) ? sourceNode.rdfTypes.filter((type: unknown): type is string => typeof type === "string") : sourceNode.rdfType ? [String(sourceNode.rdfType)] - : []; + : []).filter((t: string) => !t.startsWith('urn:vg:bnode:')); setRdfTypesState(rdfTypes); initialRdfTypesRef.current = rdfTypes.slice(); @@ -897,9 +897,9 @@ export const NodePropertyEditorContent = ({ : ""; setNodeIri(iri); - const rdfTypes: string[] = Array.isArray(sourceNode.rdfTypes) + const rdfTypes: string[] = (Array.isArray(sourceNode.rdfTypes) ? sourceNode.rdfTypes.filter((t: unknown): t is string => typeof t === "string") - : []; + : []).filter((t: string) => !t.startsWith('urn:vg:bnode:')); setRdfTypesState(rdfTypes); initialRdfTypesRef.current = rdfTypes.slice(); diff --git a/src/components/Canvas/ReactodiaCanvas.tsx b/src/components/Canvas/ReactodiaCanvas.tsx index 35822475..a974f2b3 100644 --- a/src/components/Canvas/ReactodiaCanvas.tsx +++ b/src/components/Canvas/ReactodiaCanvas.tsx @@ -44,6 +44,7 @@ import OntologyUrlAutoComplete from '../ui/OntologyUrlAutoComplete'; import { Button } from '../ui/button'; import { WELL_KNOWN_BY_PREFIX, resolveOntologyLoadUrl } from '@/utils/wellKnownOntologies'; import { instantiateWorkflowOnCanvas } from '@/utils/workflowInstantiator'; +import { LAYOUT_DEBOUNCE_MS, DEFAULT_OVERLAP_THRESHOLD_PX } from '@/utils/canvasConstants'; function extractNamespace(iri: string): string { const hash = iri.lastIndexOf('#'); @@ -224,6 +225,39 @@ function collectCanvasIris(elements: ReadonlyArray): Set, + threshold: number = DEFAULT_OVERLAP_THRESHOLD_PX, +): Set { + const groupedIris = new Set(); + for (const el of elements) { + if (el instanceof Reactodia.EntityGroup) { + for (const member of el.items) { + if (member.data.id) groupedIris.add(member.data.id); + } + } + } + const candidates = elements.filter(e => + (e instanceof Reactodia.EntityGroup) || + (e instanceof Reactodia.EntityElement && !groupedIris.has(e.data.id)) + ); + const overlapping = new Set(); + for (let i = 0; i < candidates.length; i++) { + for (let j = i + 1; j < candidates.length; j++) { + const pa = candidates[i].position; + const pb = candidates[j].position; + if (Math.abs(pa.x - pb.x) < threshold && Math.abs(pa.y - pb.y) < threshold) { + overlapping.add(candidates[i]); + overlapping.add(candidates[j]); + } + } + } + return overlapping; +} + async function applyInitialGrouping( ctx: Reactodia.WorkspaceContext, canvas: Reactodia.CanvasApi, @@ -369,6 +403,7 @@ export default function ReactodiaCanvas() { const fileInputRef = React.useRef(null); const performLayoutRef = React.useRef<(() => Promise) | null>(null); const pendingLayoutController = React.useRef(null); + const layoutDebounceTimer = React.useRef | null>(null); const initialLayoutDone = React.useRef(false); const preClusterPositions = React.useRef | null>(null); const silentLayoutPositions = React.useRef | null>(null); @@ -611,13 +646,12 @@ export default function ReactodiaCanvas() { return; } - const controller = new AbortController(); - pendingLayoutController.current = controller; - const cfg = (useAppConfigStore as any).getState().config; const layoutFn = getLayoutFunction(cfg.currentLayout, cfg, defaultLayout); if (isFullRefresh) { + const controller = new AbortController(); + pendingLayoutController.current = controller; const entityCount = model.elements.filter( (el): el is Reactodia.EntityElement => el instanceof Reactodia.EntityElement ).length; @@ -662,27 +696,29 @@ export default function ReactodiaCanvas() { actions.setCanvasReady(true); } } else { - // Incremental add: only lay out elements without a saved position. - // Elements restored from pendingPositions already have their position set. - const newElements = new Set( - model.elements.filter( - (e): e is Reactodia.EntityElement => - e instanceof Reactodia.EntityElement && - addedFiltered.includes(e.data.id) && - !pendingPositions.has(e.data.id) - ) - ); - console.debug('[canvas layout] incremental —', newElements.size, 'new elements added'); - try { - await ctx.performLayout({ - layoutFunction: layoutFn, - selectedElements: newElements, - animate: cfg.layoutAnimations, - signal: controller.signal, - }); - } finally { - if (pendingLayoutController.current === controller) pendingLayoutController.current = null; - } + // Debounce overlap-triggered layout: wait 300 ms after the last change so + // rapid sequential adds (MCP loop) and workflow drops (which call performLayout + // themselves) coalesce into a single layout run — or none if the caller already + // positioned everything. + if (layoutDebounceTimer.current) clearTimeout(layoutDebounceTimer.current); + layoutDebounceTimer.current = setTimeout(async () => { + layoutDebounceTimer.current = null; + const overlapping = findOverlappingEntities(model.elements, cfg.layoutSpacing); + console.debug('[canvas layout] overlap check (debounced) —', overlapping.size, 'overlapping'); + if (overlapping.size === 0) return; + const debouncedController = new AbortController(); + pendingLayoutController.current = debouncedController; + try { + await ctx.performLayout({ + layoutFunction: layoutFn, + selectedElements: overlapping, + animate: cfg.layoutAnimations, + signal: debouncedController.signal, + }); + } finally { + if (pendingLayoutController.current === debouncedController) pendingLayoutController.current = null; + } + }, 500); } }); }; @@ -762,14 +798,11 @@ export default function ReactodiaCanvas() { if (newIris.length > 0) { await model.requestData(); if (autoApplyLayout) { - // Layout only the elements without a saved position - const newElements = new Set( - model.elements.filter( - (e): e is Reactodia.EntityElement => - e instanceof Reactodia.EntityElement && newIris.includes(e.data.id) - ) - ); - await ctx.performLayout({ layoutFunction: layoutFn, selectedElements: newElements, animate: cfg.layoutAnimations, signal: controller.signal }); + const overlapping = findOverlappingEntities(model.elements, cfg.layoutSpacing); + console.debug('[canvas layout] view-switch new nodes —', newIris.length, 'new,', overlapping.size, 'overlapping'); + if (overlapping.size > 0) { + await ctx.performLayout({ layoutFunction: layoutFn, selectedElements: overlapping, animate: cfg.layoutAnimations, signal: controller.signal }); + } } } } else { @@ -793,7 +826,12 @@ export default function ReactodiaCanvas() { await performInitialClustering(ctx, model, cfg, layoutFn, silentLayoutPositions, preClusterPositions); setIsClustered(true); actions.setIsClustered(true); } else { - await ctx.performLayout({ layoutFunction: layoutFn, animate: cfg.layoutAnimations, signal: controller.signal }); + // First time in mode: all nodes land at default positions — lay out only overlapping ones. + const overlapping = findOverlappingEntities(model.elements, cfg.layoutSpacing); + console.debug('[canvas layout] view-switch first-time —', entityCount, 'entities,', overlapping.size, 'overlapping'); + if (overlapping.size > 0) { + await ctx.performLayout({ layoutFunction: layoutFn, selectedElements: overlapping, animate: cfg.layoutAnimations, signal: controller.signal }); + } } initialLayoutDone.current = true; actions.setCanvasReady(true); @@ -854,13 +892,22 @@ export default function ReactodiaCanvas() { startupUrl = ''; } - // ?ontology= comma-separated list of well-known prefix names (e.g. "bfo,dcat") or full URIs. + // ?ontologies= (plural) — comma-separated prefixes/URIs that REPLACE the configured + // additionalOntologies list. When present, the stored autoload list is skipped entirely. + // ?ontology= (singular) — adds on top of the configured list (existing behaviour). let startupOntologyEntries: { input: string; resolved: string; label: string }[] = []; + let ontologiesParamOverride: string[] | null = null; // non-null → replace mode try { const u = new URL(String(window.location.href)); - const ontologyParam = u.searchParams.get('ontology') || u.searchParams.get('ontologies') || ''; - if (ontologyParam.trim()) { - startupOntologyEntries = ontologyParam + const replaceParam = u.searchParams.get('ontologies'); + const addParam = u.searchParams.get('ontology'); + if (replaceParam !== null) { + // Replace mode: ?ontologies= overrides the stored additionalOntologies list. + ontologiesParamOverride = replaceParam + .split(',').map((s) => s.trim()).filter(Boolean) + .map((s) => resolveOntologyLoadUrl(s)); + } else if (addParam?.trim()) { + startupOntologyEntries = addParam .split(',') .map((s) => s.trim()) .filter(Boolean) @@ -875,11 +922,12 @@ export default function ReactodiaCanvas() { } (async () => { - // Autoload configured ontologies if enabled - if (additional.length > 0 && cfg?.persistedAutoload) { + // Autoload ontologies: ?ontologies= replaces the configured list; otherwise use config. + const autoloadList = ontologiesParamOverride ?? (cfg?.persistedAutoload ? additional : []); + if (autoloadList.length > 0) { try { actions.setLoading(true, 5, 'Autoloading configured ontologies...'); - await loadAdditionalOntologies(additional, (progress: number, message: string) => { + await loadAdditionalOntologies(autoloadList, (progress: number, message: string) => { actions.setLoading(true, Math.max(5, progress), message); }); } catch (err) { diff --git a/src/components/Canvas/RelaySection.tsx b/src/components/Canvas/RelaySection.tsx index 2ec935f9..fe0bcb0a 100644 --- a/src/components/Canvas/RelaySection.tsx +++ b/src/components/Canvas/RelaySection.tsx @@ -19,7 +19,7 @@ function fallbackCopy(text: string) { } const STARTER_PROMPT = -`You are connected to Ontosphere via a relay. A script in this tab intercepts your tool calls, runs them in Ontosphere, and injects results back as a user message. +`You are connected to Ontosphere via a relay. A script in this tab intercepts your tool calls, runs them in Ontosphere, and injects results back as a user message. If a tool call returns success:false, read the error, fix the argument, and retry the same call immediately — never skip a failed call. Output format — one JSON-RPC 2.0 call per line, backtick-wrapped: \`{"jsonrpc":"2.0","id":,"method":"tools/call","params":{"name":"","arguments":{...}}}\` diff --git a/src/components/Canvas/rdfPropertyEditor.tsx b/src/components/Canvas/rdfPropertyEditor.tsx index 8983b824..bfd9e578 100644 --- a/src/components/Canvas/rdfPropertyEditor.tsx +++ b/src/components/Canvas/rdfPropertyEditor.tsx @@ -133,13 +133,13 @@ const EntityEditor = ({ options }: EntityEditorProps) => { const entityId = elementData?.id ?? ''; const [nodeIri, setNodeIri] = useState(entityId); - const [types, setTypes] = useState(elementData ? [...elementData.types] : []); + const [types, setTypes] = useState(elementData ? elementData.types.filter(t => !t.startsWith('urn:vg:bnode:')) : []); const [rows, setRows] = useState(() => elementData ? rowsFromElementModel(elementData) : []); // Re-initialize when a different entity is opened useEffect(() => { setNodeIri(entityId); - setTypes(elementData ? [...elementData.types] : []); + setTypes(elementData ? elementData.types.filter(t => !t.startsWith('urn:vg:bnode:')) : []); setRows(elementData ? rowsFromElementModel(elementData) : []); // eslint-disable-next-line react-hooks/exhaustive-deps }, [entityId]); diff --git a/src/mcp/__tests__/graph.test.ts b/src/mcp/__tests__/graph.test.ts index d539263a..43025e81 100644 --- a/src/mcp/__tests__/graph.test.ts +++ b/src/mcp/__tests__/graph.test.ts @@ -76,7 +76,10 @@ describe('loadRdf', () => { it('calls loadRDFIntoGraph when turtle is provided', async () => { const turtle = '@prefix ex: .'; const result = await tool('loadRdf').handler({ turtle }); - expect(rdfManager.loadRDFIntoGraph).toHaveBeenCalledWith(turtle, 'urn:vg:data', 'text/turtle'); + // injectTurtlePrefixes prepends missing built-in prefixes before parsing + const [injectedTurtle] = (rdfManager.loadRDFIntoGraph as ReturnType).mock.calls[0]; + expect(injectedTurtle).toContain('@prefix ex: .'); + expect(rdfManager.loadRDFIntoGraph).toHaveBeenCalledWith(injectedTurtle, 'urn:vg:data', 'text/turtle'); expect(result).toMatchObject({ success: true, data: expect.objectContaining({ loaded: 'inline turtle' }), diff --git a/src/mcp/__tests__/links.test.ts b/src/mcp/__tests__/links.test.ts index d31b200f..f213f9ef 100644 --- a/src/mcp/__tests__/links.test.ts +++ b/src/mcp/__tests__/links.test.ts @@ -10,20 +10,16 @@ vi.mock('@/utils/rdfManager', () => ({ }, })); -// Mock workspaceContext — addLink needs getWorkspaceRefs for requestLinks +// Mock workspaceContext — addTriple needs getWorkspaceRefs for requestLinks + navigateToIri vi.mock('@/mcp/workspaceContext', () => ({ getWorkspaceRefs: vi.fn(), })); -vi.mock('@/mcp/tools/layout', () => ({ - focusElementOnCanvas: vi.fn(), -})); - import { rdfManager } from '@/utils/rdfManager'; import { getWorkspaceRefs } from '@/mcp/workspaceContext'; import { linkTools } from '../tools/links'; -const addLink = linkTools.find((t) => t.name === 'addLink')!; +const addLink = linkTools.find((t) => t.name === 'addTriple')!; const removeLink = linkTools.find((t) => t.name === 'removeLink')!; const getLinks = linkTools.find((t) => t.name === 'getLinks')!; @@ -31,10 +27,11 @@ beforeEach(() => { vi.clearAllMocks(); vi.mocked(getWorkspaceRefs).mockReturnValue({ ctx: { model: { elements: [], links: [], requestLinks: vi.fn().mockResolvedValue(undefined) } }, + navigateToIri: vi.fn(), } as never); }); -describe('addLink', () => { +describe('addTriple', () => { it('calls addTriple with correct args and returns success', async () => { const result = await addLink.handler({ subjectIri: 'http://s', @@ -52,7 +49,7 @@ describe('addLink', () => { const result = await addLink.handler({ subjectIri: 'http://s', predicateIri: 'http://p' }); expect(result).toEqual({ success: false, - error: 'subjectIri, predicateIri, and objectIri are all required. Call help({tool:"addLink"}) for the full schema.', + error: 'subjectIri, predicateIri, and objectIri are all required. Call help({tool:"addTriple"}) for the full schema.', }); expect(rdfManager.addTriple).not.toHaveBeenCalled(); }); @@ -61,7 +58,7 @@ describe('addLink', () => { const result = await addLink.handler(null); expect(result).toEqual({ success: false, - error: 'subjectIri, predicateIri, and objectIri are all required. Call help({tool:"addLink"}) for the full schema.', + error: 'subjectIri, predicateIri, and objectIri are all required. Call help({tool:"addTriple"}) for the full schema.', }); }); }); diff --git a/src/mcp/manifest.ts b/src/mcp/manifest.ts index 2f9cc9b3..66d4f787 100644 --- a/src/mcp/manifest.ts +++ b/src/mcp/manifest.ts @@ -10,7 +10,7 @@ export const mcpServerDescription = '• Ontology discovery first: loadOntology(query="use case") → loadOntology(url="") × N → setNamespace × N. foaf: is pre-loaded. OWL/RDFS/RDF/XSD always available.\n' + '• Prefer domain ontologies over schema.org: foaf:Person, ical:Vevent, org:Organization, bot:Building.\n' + '• For 5+ individuals use loadRdf(turtle=...) instead of N×addNode — one round-trip.\n' + - '• addLink blank-node limit: use loadRdf for owl:someValuesFrom/equivalentClass restrictions.\n' + + '• addTriple blank-node limit: use loadRdf for owl:someValuesFrom/equivalentClass restrictions.\n' + '• Batch up to 5 non-dependent calls per relay message. Send discovery (getNodes, queryGraph) alone.\n' + '• Tool failed? Call help({tool:""}) for the exact parameter schema.\n\n' + 'GRAPH ARCHITECTURE\n' + @@ -21,8 +21,8 @@ export const mcpServerDescription = 'After loadOntology or setNamespace, those prefixes work in all tool IRI arguments.\n\n' + 'Recommended workflow:\n' + 'loadOntology(query=…) → loadOntology(url="") × N → setNamespace × N\n' + - '→ setViewMode("tbox") → addNode × N (owl:Class) → addLink × N → runLayout\n' + - '→ setViewMode("abox") → loadRdf(turtle=...) OR addNode × N → addLink × N → runLayout\n' + + '→ setViewMode("tbox") → addNode × N (owl:Class) → addTriple × N → runLayout\n' + + '→ setViewMode("abox") → loadRdf(turtle=...) OR addNode × N → addTriple × N → runLayout\n' + '→ runReasoning → fitCanvas + exportImage(svg) [last three safe to batch]\n\n' + 'Agent integration: (1) Claude Code / Playwright — window.__mcpTools[name](params) via browser_evaluate. ' + '(2) AI Relay Bridge — any AI chat controls Ontosphere via bookmarklet relay; see docs/relay-bridge.md and AGENTS.md.'; @@ -152,7 +152,7 @@ export const mcpManifest: McpToolManifestEntry[] = [ }, { name: 'addNode', - description: 'Create a node (RDF subject) on the canvas by writing triples to the store. The node appears in the view that matches its rdf:type — owl:Class/owl:ObjectProperty etc. → TBox view; owl:NamedIndividual or unrecognised types → ABox view. Switch to the correct view with setViewMode BEFORE calling addNode so the node materialises immediately. For 5+ individuals use loadRdf(turtle=...) instead.', + description: 'Create a NEW entity on the canvas. Writes rdf:type + rdfs:label atomically so the canvas pipeline fires once, then auto-navigates to the new node. The node appears in the view matching its rdf:type (owl:Class → TBox; owl:ObjectProperty → TBox; owl:AnnotationProperty → TBox; owl:NamedIndividual or unknown → ABox). Object and annotation properties are first-class OWL entities — create them as nodes with typeIri=owl:ObjectProperty, then assert rdfs:domain and rdfs:range on them via addTriple. Use setViewMode before calling addNode. For 5+ individuals use loadRdf(turtle=...) instead. Use addTriple (not addNode) to add properties or relationships to an entity that already exists.', inputSchema: { type: 'object', properties: { @@ -187,7 +187,7 @@ export const mcpManifest: McpToolManifestEntry[] = [ }, { name: 'getNodes', - description: 'Discover or filter all known entities (TBox classes and ABox individuals). Use labelContains to find by name — falls back to fuzzy match if nothing found (response includes fuzzyFallback:true). Use typeIri to filter by class, focusFirst:true to pan the viewport to the first canvas match. Use this before addNode/addLink to resolve IRIs.', + description: 'Discover or filter all known entities (TBox classes and ABox individuals). Use labelContains to find by name — falls back to fuzzy match if nothing found (response includes fuzzyFallback:true). Use typeIri to filter by class, focusFirst:true to pan the viewport to the first canvas match. Use this before addNode/addTriple to resolve IRIs.', inputSchema: { type: 'object', properties: { @@ -199,15 +199,18 @@ export const mcpManifest: McpToolManifestEntry[] = [ }, }, { - name: 'addLink', + name: 'addTriple', description: - 'Add a directed object-property triple (edge) between two canvas nodes: subjectIri --predicateIri--> objectIri. The edge appears on canvas immediately. Both subject and object must already be canvas nodes. ' + - 'BLANK NODE LIMITATION: addLink only connects named (IRI) nodes. OWL axioms that require blank nodes — owl:someValuesFrom, owl:allValuesFrom, owl:hasValue, owl:onProperty restrictions, owl:intersectionOf, owl:unionOf, owl:equivalentClass with anonymous restrictions — CANNOT be added with addLink. Use loadRdf with inline Turtle instead (see loadRdf description).', + 'Assert any RDF triple on entities that already exist on the canvas. ' + + 'IRI object → object-property edge, renders on canvas immediately (both nodes must already exist — call addNode first if needed). ' + + 'Literal object → annotation property (rdfs:comment, skos:definition, etc.), visible after expandNode. ' + + 'Use addNode (not addTriple) when creating an entity for the first time — addNode writes type+label atomically and navigates to the new node. ' + + 'BLANK NODE LIMITATION: addTriple cannot express OWL axioms requiring blank nodes (owl:someValuesFrom, owl:allValuesFrom, owl:hasValue restrictions, owl:intersectionOf, owl:unionOf). Use loadRdf with inline Turtle instead.', inputSchema: { type: 'object', properties: { subjectIri: { type: 'string' }, - predicateIri: { type: 'string', description: 'IRI of the object property (e.g. foaf:knows)' }, + predicateIri: { type: 'string', description: 'IRI of the predicate (e.g. rdfs:subClassOf, owl:disjointWith, rdfs:domain)' }, objectIri: { type: 'string' }, }, required: ['subjectIri', 'predicateIri', 'objectIri'], @@ -326,7 +329,7 @@ export const mcpManifest: McpToolManifestEntry[] = [ }, { name: 'getNodeDetails', - description: 'Fetch every asserted triple for one entity (from urn:vg:data only, not inferred). Use when you need property values, not just the IRI — call getNodes first to find the IRI.', + description: 'Fetch every triple for one entity — asserted (urn:vg:data) and inferred (urn:vg:inferred). Inferred triples are marked inferred:true. Use after runReasoning to inspect what was derived. Call getNodes first to find the IRI.', inputSchema: { type: 'object', required: ['iri'], diff --git a/src/mcp/relayBridge.ts b/src/mcp/relayBridge.ts index 59c748c2..82a848d6 100644 --- a/src/mcp/relayBridge.ts +++ b/src/mcp/relayBridge.ts @@ -82,7 +82,7 @@ function toastLabel(tool: string, result: McpResult): string { return `${tool} · ${shortIri(d.updated) ?? ''}`; case 'expandAll': return typeof d.expanded === 'number' ? `${tool} · ${d.expanded} nodes` : tool; - case 'addLink': { + case 'addTriple': { const rel = d.added as Record | undefined; return `${tool} · ${shortIri(rel?.p) ?? ''}`; } @@ -90,6 +90,7 @@ function toastLabel(tool: string, result: McpResult): string { const rel = d.removed as Record | undefined; return `${tool} · ${shortIri(rel?.p) ?? ''}`; } + case 'runLayout': case 'setLayout': case 'layout': return typeof d.algorithm === 'string' ? `${tool} · ${d.algorithm}` : tool; @@ -108,27 +109,43 @@ async function waitForMcpTools(retries: number): Promise Promise> ): Promise { try { - const handler = tools['getGraphState']; - if (!handler) return undefined; - const result = await handler({}); - if (!result.success || !result.data) return undefined; - const d = result.data as { - nodeCount: number; - linkCount: number; - nodes: Array<{ label?: string; iri: string }>; - }; + // Use getNodes (full RDF store) so count reflects all addNode calls, + // not just nodes placed on the visual canvas. + const nodesHandler = tools['getNodes']; + const linksHandler = tools['getLinks']; + if (!nodesHandler) return undefined; + + const nodesResult = await nodesHandler({}); + if (!nodesResult.success || !nodesResult.data) return undefined; + const rawNodes = nodesResult.data as { content: string } | Array; + const nodes: Array<{ iri: string; label?: string }> = + typeof (rawNodes as { content: string }).content === 'string' + ? (JSON.parse((rawNodes as { content: string }).content) as Array<{ iri: string; label?: string }>) + : (rawNodes as Array<{ iri: string; label?: string }>); + + let linkCount = 0; + if (linksHandler) { + const linksResult = await linksHandler({}); + if (linksResult.success && linksResult.data) { + const d = linksResult.data as { links?: Array; content?: string }; + if (Array.isArray(d.links)) linkCount = d.links.length; + else if (typeof d.content === 'string') linkCount = (JSON.parse(d.content) as Array).length; + } + } + + const nodeCount = nodes.length; const MAX_LABELS = 8; - const labels = d.nodes + const labels = nodes .slice(0, MAX_LABELS) .map(n => n.label || n.iri.split(/[/#]/).pop() || n.iri) .join(', '); - const more = d.nodeCount > MAX_LABELS ? ` +${d.nodeCount - MAX_LABELS} more` : ''; - return `Canvas: ${d.nodeCount} node${d.nodeCount !== 1 ? 's' : ''} (${labels}${more}), ${d.linkCount} link${d.linkCount !== 1 ? 's' : ''}`; + const more = nodeCount > MAX_LABELS ? ` +${nodeCount - MAX_LABELS} more` : ''; + return `Store: ${nodeCount} node${nodeCount !== 1 ? 's' : ''} (${labels}${more}), ${linkCount} link${linkCount !== 1 ? 's' : ''}`; } catch { return undefined; } @@ -157,6 +174,7 @@ async function handleCall( const handler = tools[tool]; if (!handler) { const error = `Unknown tool: ${tool}`; + console.error('[RelayBridge] Unknown tool:', tool); channel.postMessage({ type: 'vg-result', requestId, result: { success: false, error } }); channel.postMessage({ type: 'vg-ready' }); toast.error(`✗ ${tool}: ${error}`); @@ -173,9 +191,11 @@ async function handleCall( let result: McpResult; try { result = await handler(params); + console.info('[RelayBridge] Tool result:', tool, JSON.stringify(result).slice(0, 300)); } catch (err) { clearInterval(pingInterval); const error = err instanceof Error ? err.message : String(err); + console.error('[RelayBridge] Tool error:', tool, JSON.stringify(params).slice(0, 200), error); result = { success: false, error }; channel.postMessage({ type: 'vg-result', requestId, result }); channel.postMessage({ type: 'vg-ready' }); @@ -264,7 +284,7 @@ export function startRelayBridge(): () => void { channel.onmessage = (event: MessageEvent) => { const msg = event.data; - console.info('[RelayBridge] BC message received:', msg); + console.info('[RelayBridge] BC message received:', JSON.stringify(msg)); if (msg?.type === 'vg-ready') { // Initial vg-ready from workspaceContext signals the app is ready for first call diff --git a/src/mcp/tools/graph.ts b/src/mcp/tools/graph.ts index b1df58b2..38b6bd71 100644 --- a/src/mcp/tools/graph.ts +++ b/src/mcp/tools/graph.ts @@ -7,6 +7,8 @@ import { mcpManifest, mcpServerDescription } from '@/mcp/manifest'; import { Parser as SparqlParser, Generator as SparqlGenerator } from 'sparqljs'; import { resolveOntologyLoadUrl, searchWellKnownOntologies, searchOntologyPacks } from '@/utils/wellKnownOntologies'; import { useOntologyStore } from '@/stores/ontologyStore'; +import { LOAD_RDF_PROPAGATION_DELAY_MS } from '@/utils/canvasConstants'; +import { BUILTIN_PREFIXES } from '@/mcp/tools/iriUtils'; /** Fix PREFIX declarations where the IRI is bare (no angle brackets): PREFIX rdf: http://... → PREFIX rdf: */ function normalizePrefixIris(sparql: string): string { @@ -19,12 +21,24 @@ function normalizePrefixIris(sparql: string): string { /** Prepend PREFIX declarations from the namespace map for any prefix not already declared in the query. */ function injectPrefixes(sparql: string): string { const normalized = normalizePrefixIris(sparql); - const namespaces = rdfManager.getNamespaces(); + const dynamicNamespaces = rdfManager.getNamespaces(); const declared = new Set(); for (const m of normalized.matchAll(/(?:PREFIX|BASE)\s+(\S+)\s*:/gi)) declared.add(m[1].toLowerCase()); - const lines = namespaces - .filter(ns => ns.prefix && ns.uri && !declared.has(ns.prefix.toLowerCase())) - .map(ns => `PREFIX ${ns.prefix}: <${ns.uri}>`); + // Merge built-in prefixes (e.g. ex:, owl:) with dynamic registry, dynamic takes precedence. + const builtinEntries = Object.entries(BUILTIN_PREFIXES).map(([k, v]) => ({ + prefix: k.replace(/:$/, ''), + uri: v, + })); + const merged = [...builtinEntries, ...dynamicNamespaces]; + const seen = new Set(); + const lines: string[] = []; + for (const ns of merged) { + if (!ns.prefix || !ns.uri) continue; + const key = ns.prefix.toLowerCase(); + if (seen.has(key) || declared.has(key)) continue; + seen.add(key); + lines.push(`PREFIX ${ns.prefix}: <${ns.uri}>`); + } return lines.length ? `${lines.join('\n')}\n${normalized}` : normalized; } @@ -45,14 +59,50 @@ function getCanvasIris(): string[] { // --------------------------------------------------------------------------- // loadRdf // --------------------------------------------------------------------------- + +/** + * Check the inline Turtle for common model-output mistakes and return a + * descriptive error string, or null if the snippet looks plausible. + * Does NOT attempt to parse Turtle — only catches obvious patterns before N3.js runs. + */ +function validateTurtleSnippet(turtle: string): string | null { + // Detect @prefix declarations whose IRI is missing (e.g. "@prefix owl: .") + for (const m of turtle.matchAll(/@prefix\s+(\w*)\s*:\s*\./g)) { + const pfx = m[1]; + const builtin = BUILTIN_PREFIXES[pfx + ':']; + const hint = builtin + ? ` Either use '@prefix ${pfx}: <${builtin}> .' or omit @prefix lines entirely — ${pfx}:, owl:, rdf:, rdfs:, ex:, xsd: are auto-injected.` + : ` IRI is required: '@prefix ${pfx}: .'`; + return `Invalid @prefix declaration '@prefix ${pfx}: .' — the namespace IRI is missing.${hint}`; + } + // Detect bare full IRIs used as subjects (http://... without angle brackets) + for (const m of turtle.matchAll(/^(https?:\/\/\S+)/mg)) { + const bare = m[1]; + const exMatch = bare.match(/^https?:\/\/example\.org\/(.+)/); + const altHint = exMatch ? ` — or use prefix notation 'ex:${exMatch[1]}'` : ''; + return `Bare IRI '${bare}' is not valid Turtle. Full IRIs must be wrapped in angle brackets: '<${bare}>'${altHint}.`; + } + return null; +} + +/** Prepend BUILTIN_PREFIXES for any prefix not already declared in the turtle. */ +function injectTurtlePrefixes(turtle: string): string { + const declared = new Set(); + for (const m of turtle.matchAll(/@prefix\s+(\w*)\s*:/g)) declared.add(m[1]); + const missing = Object.entries(BUILTIN_PREFIXES) + .filter(([k]) => !declared.has(k.replace(/:$/, ''))) + .map(([k, v]) => `@prefix ${k.replace(/:$/, '')}: <${v}> .`); + return missing.length > 0 ? missing.join('\n') + '\n' + turtle : turtle; +} + const loadRdf: McpTool = { name: 'loadRdf', - description: 'Load RDF data into the graph from a URL or inline Turtle text.', + description: 'Load RDF data into the graph from a URL or inline Turtle text. Common prefixes (owl:, rdf:, rdfs:, ex:, xsd:) are auto-injected — you do not need to include @prefix declarations.', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL of an RDF document to fetch and load.' }, - turtle: { type: 'string', description: 'Inline Turtle text to load.' }, + turtle: { type: 'string', description: 'Inline Turtle text to load. @prefix declarations are optional — owl:, rdf:, rdfs:, ex:, xsd: are available automatically.' }, }, oneOf: [{ required: ['url'] }, { required: ['turtle'] }], }, @@ -64,10 +114,13 @@ const loadRdf: McpTool = { return { success: true, data: { loaded: p.url } }; } if (p.turtle) { + const snippetError = validateTurtleSnippet(p.turtle); + if (snippetError) return { success: false, error: snippetError }; const canvasBefore = getCanvasIris(); - await rdfManager.loadRDFIntoGraph(p.turtle, 'urn:vg:data', 'text/turtle'); + const normalizedTurtle = injectTurtlePrefixes(p.turtle); + await rdfManager.loadRDFIntoGraph(normalizedTurtle, 'urn:vg:data', 'text/turtle'); // Wait for the RDF worker change event to propagate to dataProvider.allSubjects - await new Promise(r => setTimeout(r, 600)); + await new Promise(r => setTimeout(r, LOAD_RDF_PROPAGATION_DELAY_MS)); const { dataProvider } = getWorkspaceRefs(); const allItems = await dataProvider.lookupAll(); const canvasBeforeSet = new Set(canvasBefore); @@ -459,39 +512,84 @@ const help: McpTool = { return { success: true, data: { content: JSON.stringify({ name: entry.name, description: entry.description, inputSchema: entry.inputSchema }) } }; } const instructions = [ + '⚠️ RELAY INTERCEPTION — READ FIRST', + 'This relay ONLY intercepts JSON-RPC 2.0 wrapped in single backticks (see format below).', + 'ALL other formats are SILENTLY IGNORED — no response, no error, nothing:', + ' • OpenAI function_call / tool_calls', + ' • Claude tool_use blocks', + ' • Gemini functionCall', + ' • {"tool":"x","input":{}} style', + ' • {"jsonrpc":"2.0","method":"toolName",...} ← method must be "tools/call", not a tool name', + ' • XML tags', + ' • Plain prose describing a tool call', + 'If you do not use the exact format below, your call will never be executed.', + '', 'RELAY FORMAT', 'Single backtick per JSON-RPC object. Up to 5 calls per message — they run in order.', '`{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"TOOLNAME","arguments":ARGS}}`', '', 'Example — layout + fit + export in one message:', - '`{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"runLayout","arguments":{"algorithm":"dagre-lr"}}}`', + '`{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"runLayout","arguments":{}}}`', '`{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"fitCanvas","arguments":{}}}`', '`{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"exportImage","arguments":{"format":"svg"}}}`', '', 'CRITICAL RULES', '1. Single ` not triple ```. Increment id per call. Never output a call you don\'t intend to run.', + ' ⚠️ EXAMPLE CALLS ARE EXECUTED: any backtick-wrapped JSON-RPC in your message is run immediately.', + ' In explanatory text to the user, describe actions in plain prose — never show a sample call.', '2. Batch up to 5 non-dependent calls (mutations, layout, export). Send discovery calls (getNodes, queryGraph) alone and wait for the result before continuing.', - '3. addLink: both nodes must exist first — call addNode for each endpoint in a prior batch or earlier in the same batch.', - '4. 5+ individuals: loadRdf(turtle=...) not repeated addNode — one round-trip.', - '5. Pre-loaded prefixes: foaf rdf rdfs owl xsd skos dc ex — use short form (foaf:Person, owl:Class).', - '6. Tool failed? Call help({tool:"toolname"}) to get the exact parameter schema.', + '3. addTriple (IRI-object edges): both nodes must exist first — call addNode for each endpoint before or in the same batch as addTriple.', + '4. addTriple (literal object): annotation property — visible on canvas after expandNode.', + '5. 5+ individuals: loadRdf(turtle=...) not repeated addNode — one round-trip.', + '6. Pre-loaded prefixes: foaf rdf rdfs owl xsd skos dc ex — use short form (foaf:Person, owl:Class).', + '7. Tool failed? READ THE ERROR and retry immediately with the corrected call. Never skip a failed call — keep retrying until success or you have exhausted the fix options, then report what failed and why.', + '8. GUIDED SESSION: if the user is asking one question at a time, execute ONLY what was asked, then STOP. Do not execute additional tools based on relay results. Wait for the next user question.', + ' Skip suggestOntologiesForTask and loadOntology unless explicitly requested — guided sessions provide all context through questions.', '', 'COMMON MISTAKES', - 'WRONG: addLink({s:"ex:A", p:"foaf:knows", o:"ex:B"})', - 'RIGHT: addLink({subjectIri:"ex:A", predicateIri:"foaf:knows", objectIri:"ex:B"})', + 'WRONG: addLink({...}) ← this tool does not exist; use addTriple for all RDF edges', + 'RIGHT: addTriple({subjectIri:"ex:A", predicateIri:"rdfs:subClassOf", objectIri:"ex:B"})', + '', + 'WRONG: addTriple({s:"ex:A", p:"rdfs:subClassOf", o:"ex:B"})', + 'RIGHT: addTriple({subjectIri:"ex:A", predicateIri:"rdfs:subClassOf", objectIri:"ex:B"})', + '', + 'WRONG: addTriple for a brand-new entity ← use addNode to create, then addTriple to link', + 'RIGHT: addNode({iri:"ex:MyClass", typeIri:"owl:Class", label:"MyClass"}) then addTriple({subjectIri:"ex:MyClass",...})', '', 'WRONG: loadOntology({url:"calendar"}) ← searches by prefix name, not use-case', 'RIGHT: loadOntology({query:"calendar"}) then loadOntology({url:"ical"}) to load', '', + 'WRONG: queryGraph({query:"SELECT * WHERE {?s ?p ?o}"})', + 'RIGHT: queryGraph({sparql:"SELECT * WHERE {?s ?p ?o}"}) ← param is "sparql", not "query"', + '', + 'WRONG: setViewMode({viewMode:"abox"})', + 'RIGHT: setViewMode({mode:"abox"}) ← param is "mode", not "viewMode"', + '', + 'WRONG: setLayout({...}), layout({...}), hierarchical({...}) ← these tools do not exist', + 'RIGHT: runLayout({}) ← the only layout tool; call it after adding nodes', + '', + 'WRONG (silently ignored): {"tool":"addNode","input":{"iri":"ex:MyClass"}}', + 'WRONG (silently ignored): any native tool/function-call syntax your model normally uses', + 'WRONG (silently ignored): {"jsonrpc":"2.0","method":"addNode","params":{"iri":"ex:MyClass"},"id":1} ← method must be "tools/call", NOT the tool name', + 'RIGHT: `{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"addNode","arguments":{"iri":"ex:MyClass","typeIri":"owl:Class"}}}`', + '', 'SPARQL: prefixes registered via loadOntology/setNamespace are auto-injected — no PREFIX declarations needed for them.', 'Only declare a PREFIX if it is not in your namespace registry.', '', + 'CANVAS — PROACTIVE RULES (do these automatically, without being asked)', + '• addNode auto-navigates to the new node — no separate focusNode call needed.', + '• After adding any nodes: call runLayout({}) — without it nodes pile at (0,0).', + '• runLayout is view-specific — only arranges the currently active view.', + ' After setViewMode, call runLayout({}) again to arrange the new view.', + '• Batch pattern: addNode(s) + addTriple(s) in one message, then runLayout({}) in the next.', + '• Annotation literals: call expandNode({iri:"..."}) after addTriple to reveal them on the node card.', + '', 'WORKFLOW (minimal working session)', '1. suggestOntologiesForTask({task:"..."}) → pick prefixes', '2. loadOntology({url:""}) × N [batch these]', - '3. setViewMode({mode:"abox"})', - '4. loadRdf({turtle:"..."}) OR addNode × N + addLink × N [batch builds; discovery alone]', - '5. runLayout({algorithm:"dagre-lr"}) + fitCanvas() + exportImage({format:"svg"}) [batch these]', + '3. addNode × N + addTriple × N [batch builds; discovery alone]', + '4. runLayout({}) + fitCanvas() + exportImage({format:"svg"}) [batch these]', + ' Use setViewMode({mode:"abox"}) only when explicitly working with individuals.', '', 'READING RESULTS', 'Relay injects [Ontosphere — N tools ✓] with one backtick-wrapped JSON-RPC response per call.', diff --git a/src/mcp/tools/iriUtils.ts b/src/mcp/tools/iriUtils.ts index 7b713346..aabd1a22 100644 --- a/src/mcp/tools/iriUtils.ts +++ b/src/mcp/tools/iriUtils.ts @@ -3,7 +3,7 @@ // Dynamic namespaces (from addNamespace / loadOntology) are provided via // setNamespaceRegistryGetter, wired up by ontosphereMcpServer.ts at startup. -const BUILTIN_PREFIXES: Record = { +export const BUILTIN_PREFIXES: Record = { 'rdf:': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', 'rdfs:': 'http://www.w3.org/2000/01/rdf-schema#', 'owl:': 'http://www.w3.org/2002/07/owl#', @@ -53,6 +53,8 @@ const URI_SCHEMES = ['http:', 'https:', 'urn:', 'mailto:', 'ftp:', 'file:', 'urn */ export function expandIri(value: string): string { if (!value) return value; + // Blank node identifiers pass through unchanged. + if (value.startsWith('_:')) return value; // Pass through any value whose scheme is a known URI scheme (not an RDF prefix). for (const scheme of URI_SCHEMES) { if (value.startsWith(scheme)) return value; diff --git a/src/mcp/tools/links.ts b/src/mcp/tools/links.ts index fd13bebc..2aaaa334 100644 --- a/src/mcp/tools/links.ts +++ b/src/mcp/tools/links.ts @@ -2,7 +2,6 @@ import type { McpTool } from '../types'; import { rdfManager } from '@/utils/rdfManager'; import { getWorkspaceRefs } from '@/mcp/workspaceContext'; -import { focusElementOnCanvas } from './layout'; import { expandIri } from './iriUtils'; import * as Reactodia from '@reactodia/workspace'; @@ -19,8 +18,8 @@ interface LinkParams { export const linkTools: McpTool[] = [ { - name: 'addLink', - description: 'Add a triple (directed edge) between two entities.', + name: 'addTriple', + description: 'Add a simple RDF triple between named entities (IRIs) or attach a literal annotation. Use for: object-property links between known IRIs (e.g. ex:Pizza rdf:type ex:Food), data/annotation properties (e.g. rdfs:label "Pizza"@en), and rdf:type assertions. Object-property triples render on canvas immediately; literal triples appear when the subject node is expanded via expandNode. Do NOT use for OWL axioms that require intermediate anonymous nodes — restrictions (owl:someValuesFrom, owl:allValuesFrom, owl:hasValue), intersections (owl:intersectionOf), unions, or other complex class expressions. For those, use loadRdf with inline Turtle: loadRdf(`@prefix ex: . @prefix owl: . ex:SalamiPizza owl:equivalentClass [ rdf:type owl:Restriction ; owl:onProperty ex:hasPart ; owl:someValuesFrom ex:SalamiTopping ] .`). Blank node labels ("_:b0") are accepted and skolemized consistently, but only for simple entity linking — do not use them to build restriction structures via addTriple.', inputSchema: { type: 'object', properties: { @@ -41,10 +40,23 @@ export const linkTools: McpTool[] = [ // objectIri may be a plain literal — only expand if it looks like a prefixed IRI const objectIri = rawO ? expandIri(rawO) : undefined; if (!subjectIri || !predicateIri || !objectIri) { - return { success: false as const, error: 'subjectIri, predicateIri, and objectIri are all required. Call help({tool:"addLink"}) for the full schema.' }; + return { success: false as const, error: 'subjectIri, predicateIri, and objectIri are all required. Call help({tool:"addTriple"}) for the full schema.' }; } const expandError = [subjectIri, predicateIri, objectIri].find(v => v.startsWith('Unknown prefix:')); if (expandError) return { success: false as const, error: expandError }; + if (subjectIri.startsWith('[') || objectIri.startsWith('[')) { + return { success: false as const, error: 'Inline Turtle blank node syntax "[ ... ]" is not a valid IRI. Use an explicit blank node label instead, e.g. "_:b0". Call addTriple("_:b0","rdf:type","owl:Restriction") then addTriple("_:b0","owl:onProperty","ex:hasPart") etc. Each distinct restriction needs a distinct label.' }; + } + // IRIs never contain spaces — catch Turtle fragments passed as IRIs + // (e.g. "ex:hasPart someValuesFrom ex:Foo" or "Pizza and hasTopping some Foo") + if (/ /.test(subjectIri) || / /.test(objectIri)) { + const badIri = [subjectIri, objectIri].find(v => / /.test(v)) ?? ''; + // Detect corrupted blank-node: leading whitespace before colon (e.g. " :r1" → should be "_:r1") + const blankNodeHint = /^\s+:/.test(badIri) + ? ` You passed "${badIri}" — this is a blank-node label with leading spaces instead of underscore. Use "_:${badIri.trim().slice(1)}" (must start with "_:", not spaces).` + : ` You passed "${badIri}".`; + return { success: false as const, error: `IRI contains spaces and is not valid.${blankNodeHint} For OWL restrictions prefer loadRdf with Turtle: loadRdf({turtle:"@prefix ex: . @prefix owl: . ex:SalamiPizza owl:equivalentClass [ a owl:Restriction ; owl:onProperty ex:hasPart ; owl:someValuesFrom ex:SalamiTopping ] ."})` }; + } rdfManager.addTriple(subjectIri, predicateIri, objectIri); const { ctx } = getWorkspaceRefs(); @@ -53,10 +65,8 @@ export const linkTools: McpTool[] = [ addedElements: [subjectIri as Reactodia.ElementIri, objectIri as Reactodia.ElementIri], }); - const subjectEl = model.elements.find( - e => e instanceof Reactodia.EntityElement && (e as Reactodia.EntityElement).iri === subjectIri - ) as Reactodia.EntityElement | undefined; - if (subjectEl) focusElementOnCanvas(subjectEl, ctx); + const { navigateToIri } = getWorkspaceRefs(); + navigateToIri?.(subjectIri); return { success: true as const, data: { added: { s: subjectIri, p: predicateIri, o: objectIri } } }; } catch (e) { diff --git a/src/mcp/tools/nodes.ts b/src/mcp/tools/nodes.ts index 80ceea2c..9de86874 100644 --- a/src/mcp/tools/nodes.ts +++ b/src/mcp/tools/nodes.ts @@ -5,6 +5,7 @@ import { rdfManager } from '@/utils/rdfManager'; import { getWorkspaceRefs } from '@/mcp/workspaceContext'; import { focusElementOnCanvas } from './layout'; import { expandIri } from './iriUtils'; +import { ADD_NODE_PIPELINE_DELAY_MS } from '@/utils/canvasConstants'; const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'; const RDFS_LABEL = 'http://www.w3.org/2000/01/rdf-schema#label'; @@ -37,6 +38,12 @@ const addNode: McpTool = { if (!raw.iri) return { success: false, error: 'iri is required' }; const iri = expandIri(raw.iri); if (iri.startsWith('Unknown prefix:')) return { success: false, error: iri }; + if (/ /.test(iri)) { + const blankNodeHint = /^\s+:/.test(iri) + ? ` You passed "${iri}" — blank-node label with leading spaces. Use "_:${iri.trim().slice(1)}" (must start with "_:", not spaces).` + : ` You passed "${iri}".`; + return { success: false, error: `Node IRI contains spaces and is not valid.${blankNodeHint}` }; + } const typeIri = (raw.typeIri ?? raw.type) ? expandIri((raw.typeIri ?? raw.type)!) : undefined; if (typeIri?.startsWith('Unknown prefix:')) return { success: false, error: typeIri }; const { label } = raw; @@ -50,6 +57,11 @@ const addNode: McpTool = { if (label) adds.push({ s: iri, p: RDFS_LABEL, o: label }); if (adds.length > 0) await rdfManager.applyBatch({ adds }); + // Wait for the RDF→canvas pipeline before navigating; navigateToIri handles view-switching. + await new Promise(r => setTimeout(r, ADD_NODE_PIPELINE_DELAY_MS)); + const { navigateToIri } = getWorkspaceRefs(); + navigateToIri?.(iri); + return { success: true, data: { iri } }; } catch (e) { return { success: false, error: String(e) }; @@ -110,6 +122,8 @@ const expandNode: McpTool = { const el = findEntityElement(iri, model); if (!el) return { success: false, error: `Element not on canvas: ${iri}` }; model.history.execute(Reactodia.setElementExpanded(el, expand)); + const { navigateToIri } = getWorkspaceRefs(); + navigateToIri?.(iri); return { success: true, data: { iri, expanded: expand } }; } catch (e) { return { success: false, error: String(e) }; @@ -209,7 +223,7 @@ function classifyObject(value: string): 'iri' | 'literal' | 'bnode' { const getNodeDetails: McpTool = { name: 'getNodeDetails', - description: 'Return all asserted RDF properties (triples) for a specific entity IRI. Also navigates the canvas to the node, switching between ABox/TBox views if needed. Only reads from the asserted graph (urn:vg:data) — inferred triples are not included.', + description: 'Return all RDF properties (triples) for a specific entity IRI — both asserted (urn:vg:data) and inferred (urn:vg:inferred). Inferred triples are marked inferred:true in the result. Also navigates the canvas to the node, switching between ABox/TBox views if needed.', inputSchema: { type: 'object', required: ['iri'], @@ -248,6 +262,9 @@ const getNodeDetails: McpTool = { } } + const { navigateToIri } = getWorkspaceRefs(); + navigateToIri?.(iri); + return { success: true, data: { iri, label, types: [...typeSet], properties } }; } catch (e) { return { success: false, error: String(e) }; diff --git a/src/providers/N3DataProvider.ts b/src/providers/N3DataProvider.ts index 1b121574..7b8ffa44 100644 --- a/src/providers/N3DataProvider.ts +++ b/src/providers/N3DataProvider.ts @@ -127,6 +127,7 @@ const SCHEMA_ALLOWED_GRAPHS = new Set([ const TBOX_TYPES = ALL_TBOX_TYPES; const RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type'; +const SKOLEM_PREFIX = 'urn:vg:bnode:'; /** IRIs of synthetic marker properties injected into ElementModel / LinkModel. */ export const INFERRED_TYPES_PROP = 'urn:vg:inferredTypes' as PropertyTypeIri; @@ -227,7 +228,8 @@ export class N3DataProvider implements DataProvider { const addToSchema = graphName === undefined || SCHEMA_ALLOWED_GRAPHS.has(graphName); for (const q of arr) { - if (q.subject.termType === 'NamedNode' && addToIndex) { + if (q.subject.termType === 'NamedNode' && addToIndex + && !q.subject.value.startsWith(SKOLEM_PREFIX)) { this.allSubjects.add(q.subject.value); } if ( @@ -235,7 +237,9 @@ export class N3DataProvider implements DataProvider { q.predicate.termType === 'NamedNode' && q.predicate.value === RDF_TYPE && q.subject.termType === 'NamedNode' && - q.object.termType === 'NamedNode' + !q.subject.value.startsWith(SKOLEM_PREFIX) && + q.object.termType === 'NamedNode' && + !q.object.value.startsWith(SKOLEM_PREFIX) ) { let types = this.typeMap.get(q.subject.value); if (!types) { types = new Set(); this.typeMap.set(q.subject.value, types); } @@ -350,10 +354,16 @@ export class N3DataProvider implements DataProvider { if (!knownIds.has(parent)) { extraTypes.push({ id: parent, label: [], count: 0 }); knownIds.add(parent); } extraEdges.push([child, parent]); } - if (extraEdges.length === 0 && extraTypes.length === 0) return graph; + const isSkolem = (id: ElementTypeIri) => String(id).startsWith(SKOLEM_PREFIX); + const filteredBase = graph.elementTypes.filter(et => !isSkolem(et.id)); + if (extraEdges.length === 0 && extraTypes.length === 0) { + return filteredBase.length === graph.elementTypes.length ? graph : { ...graph, elementTypes: filteredBase }; + } return { - elementTypes: extraTypes.length > 0 ? [...graph.elementTypes, ...extraTypes] : graph.elementTypes, - subtypeOf: [...graph.subtypeOf, ...extraEdges], + elementTypes: [...filteredBase, ...extraTypes.filter(et => !isSkolem(et.id))], + subtypeOf: [...graph.subtypeOf, ...extraEdges].filter( + ([c, p]) => !isSkolem(c as ElementTypeIri) && !isSkolem(p as ElementTypeIri) + ), }; } /** diff --git a/src/templates/RdfElementTemplate.tsx b/src/templates/RdfElementTemplate.tsx index bde5beaa..72239ac5 100644 --- a/src/templates/RdfElementTemplate.tsx +++ b/src/templates/RdfElementTemplate.tsx @@ -98,7 +98,8 @@ function RdfElementBody({ props }: { props: Reactodia.TemplateProps }) { // Type labels — prefer prefix-shortened IRI (e.g. owl:NamedIndividual), fall back to // model-loaded label, then bare local name - const typeLabels = data.types.map(typeIri => { + const displayTypes = data.types.filter(t => !t.startsWith('urn:vg:bnode:')); + const typeLabels = displayTypes.map(typeIri => { const shortened = prefixShorten(typeIri, prefixes); // prefixShorten returns the full IRI unchanged when no prefix matches if (shortened !== typeIri) return shortened; @@ -167,12 +168,12 @@ function RdfElementBody({ props }: { props: Reactodia.TemplateProps }) { textOverflow: 'ellipsis', whiteSpace: 'nowrap', }} - title={data.types.join(', ')} + title={displayTypes.join(', ')} > {typeLabels.map((label, i) => ( diff --git a/src/utils/canvasConstants.ts b/src/utils/canvasConstants.ts new file mode 100644 index 00000000..adb5c312 --- /dev/null +++ b/src/utils/canvasConstants.ts @@ -0,0 +1,18 @@ +// Timing constants shared between MCP tools and the canvas layout system. + +// How long addNode waits for the RDF→canvas pipeline to settle before calling +// navigateToIri. The layout debounce must exceed this so rapid sequential +// addNode calls coalesce into a single layout run. +export const ADD_NODE_PIPELINE_DELAY_MS = 400; + +// How long loadRdf waits for the RDF worker change event to propagate to +// dataProvider.allSubjects before querying newly loaded entities. +export const LOAD_RDF_PROPAGATION_DELAY_MS = 600; + +// Debounce window for overlap-triggered auto-layout. Must be > ADD_NODE_PIPELINE_DELAY_MS +// so back-to-back addNode calls share one layout run instead of firing N times. +export const LAYOUT_DEBOUNCE_MS = ADD_NODE_PIPELINE_DELAY_MS + 100; + +// Default overlap detection threshold (px). Matches the default layoutSpacing so +// overlap detection and layout spacing stay in sync when the user hasn't customised spacing. +export const DEFAULT_OVERLAP_THRESHOLD_PX = 120; diff --git a/src/workers/rdfManager.runtime.ts b/src/workers/rdfManager.runtime.ts index 45cda8c0..c0bede39 100644 --- a/src/workers/rdfManager.runtime.ts +++ b/src/workers/rdfManager.runtime.ts @@ -152,6 +152,45 @@ export function createRdfWorkerRuntime(postMessage: (message: unknown) => void): } } + function fnv1a32(str: string): string { + let h = 0x811c9dc5; + for (let i = 0; i < str.length; i++) { + h ^= str.charCodeAt(i); + h = Math.imul(h, 0x01000193) >>> 0; + } + return h.toString(16).padStart(8, "0"); + } + + // Skolemize: replace blank-node subjects/objects with stable urn:vg:bnode:{hash} IRIs. + // Hash is derived from the blank node's label (value) only — not its predicate-object + // pairs — so the same label always maps to the same IRI regardless of batch size. + // This enables building blank-node restrictions via individual addTriple calls: + // every call that references "_:b0" produces the same urn:vg:bnode: IRI. + // Callers are responsible for using distinct labels for distinct blank nodes. + function skolemizeQuads(quads: Quad[], DataFactory: any): Quad[] { + const hasBnodes = quads.some( + (q) => q.subject.termType === "BlankNode" || q.object.termType === "BlankNode" + ); + if (!hasBnodes) return quads; + + const cache = new Map(); + const toIri = (id: string): string => { + if (!cache.has(id)) cache.set(id, `urn:vg:bnode:${fnv1a32(id)}`); + return cache.get(id)!; + }; + + return quads.map((q) => { + const subj = q.subject.termType === "BlankNode" + ? DataFactory.namedNode(toIri(q.subject.value)) + : q.subject; + const obj = q.object.termType === "BlankNode" + ? DataFactory.namedNode(toIri((q.object as any).value)) + : q.object; + if (subj === q.subject && obj === q.object) return q; + return DataFactory.quad(subj, q.predicate, obj, q.graph); + }); + } + function resetSharedStore() { const { StoreCls, DataFactory } = resolveN3(); if (!StoreCls) throw new Error("n3-store-unavailable"); @@ -785,9 +824,9 @@ export function createRdfWorkerRuntime(postMessage: (message: unknown) => void): } if (payload && Array.isArray(payload.quads)) { - for (const pq of payload.quads) { + const rawQuads = (payload.quads as any[]).map((pq) => deserializeQuad(pq, DataFactory)); + for (const quad of skolemizeQuads(rawQuads, DataFactory)) { try { - const quad = deserializeQuad(pq as any, DataFactory); store.addQuad(quad); added += 1; touchedSubjects.add(subjectTermToString(quad.subject)); @@ -993,6 +1032,10 @@ export function createRdfWorkerRuntime(postMessage: (message: unknown) => void): const addedSerialized: WorkerQuad[] = []; let addedCount = 0; + // Buffer raw parsed quads, then skolemize as a batch after the stream + // ends — this ensures content-hash consistency across the whole parse. + const parsedBuffer: Quad[] = []; + await new Promise((resolve, reject) => { const opts: Record = {}; if (contentType) opts.contentType = contentType; @@ -1007,31 +1050,12 @@ export function createRdfWorkerRuntime(postMessage: (message: unknown) => void): !incoming.graph || !incoming.graph.termType || incoming.graph.termType === "DefaultGraph" ? targetGraph : incoming.graph; - const normalized = DataFactory.quad( + parsedBuffer.push(DataFactory.quad( incoming.subject, incoming.predicate, incoming.object, graphTerm, - ); - const exists = - typeof store.countQuads === "function" - ? store.countQuads( - normalized.subject, - normalized.predicate, - normalized.object, - normalized.graph, - ) > 0 - : (store.getQuads( - normalized.subject, - normalized.predicate, - normalized.object, - normalized.graph, - ) || []).length > 0; - if (exists) return; - store.addQuad(normalized); - addedCount += 1; - touchedSubjects.add(subjectTermToString(normalized.subject)); - addedSerialized.push(serializeQuad(normalized)); + )); } catch (err) { console.debug("[rdfManager.worker] importSerialized.data failed", err); } @@ -1060,6 +1084,33 @@ export function createRdfWorkerRuntime(postMessage: (message: unknown) => void): }); }); + // Skolemize the full parsed batch, then write to store + for (const normalized of skolemizeQuads(parsedBuffer, DataFactory)) { + try { + const exists = + typeof store.countQuads === "function" + ? store.countQuads( + normalized.subject, + normalized.predicate, + normalized.object, + normalized.graph, + ) > 0 + : (store.getQuads( + normalized.subject, + normalized.predicate, + normalized.object, + normalized.graph, + ) || []).length > 0; + if (exists) continue; + store.addQuad(normalized); + addedCount += 1; + touchedSubjects.add(subjectTermToString(normalized.subject)); + addedSerialized.push(serializeQuad(normalized)); + } catch (err) { + console.debug("[rdfManager.worker] importSerialized.data failed", err); + } + } + if ( Object.keys(prefixes).length > 0 && (graphName === "urn:vg:data" || graphName === "urn:vg:ontologies") @@ -1229,7 +1280,8 @@ export function createRdfWorkerRuntime(postMessage: (message: unknown) => void): ? (store.getQuads(null, null, null, inferredTerm) || []) : []; - const filteredInferred = rawInferred.filter((q) => { + const skolemizedInferred = skolemizeQuads(rawInferred, DataFactory); + const filteredInferred = skolemizedInferred.filter((q) => { // Must be grounded in data (subject is a NamedNode known from the data graph) if (q.subject.termType !== "NamedNode") return false; if (!dataSubjects.has(q.subject.value)) return false; @@ -1250,11 +1302,22 @@ export function createRdfWorkerRuntime(postMessage: (message: unknown) => void): if (!seenKeys.has(k)) { seenKeys.add(k); mergedQuads.push(q); } } + const deskolemized = mergedQuads.map((q) => { + const subj = q.subject.termType === "NamedNode" && q.subject.value.startsWith("urn:vg:bnode:") + ? DataFactory.blankNode(q.subject.value.slice("urn:vg:bnode:".length)) + : q.subject; + const obj = q.object.termType === "NamedNode" && q.object.value.startsWith("urn:vg:bnode:") + ? DataFactory.blankNode(q.object.value.slice("urn:vg:bnode:".length)) + : q.object; + if (subj === q.subject && obj === q.object) return q; + return DataFactory.quad(subj, q.predicate, obj, q.graph); + }); + const toWrite: Quad[] = formatInfo.dropGraph - ? mergedQuads.map((q) => + ? deskolemized.map((q) => DataFactory.quad(q.subject, q.predicate, q.object, DataFactory.defaultGraph()), ) - : mergedQuads; + : deskolemized; let output: string; @@ -1568,9 +1631,9 @@ export function createRdfWorkerRuntime(postMessage: (message: unknown) => void): ? DataFactory.namedNode(gVal) : DataFactory.defaultGraph(); - store.addQuad(subj, pred, obj, graph); + store.addQuad(DataFactory.quad(subj, pred, obj, graph)); renamed += 1; - touchedSubjects.add(newS !== null ? newS : subjectTermToString(q.subject)); + touchedSubjects.add(newS !== null ? newS : subjectTermToString(subj)); } catch (err) { console.debug("[rdfManager.worker] renameNamespaceUri failed for quad", err); } @@ -1803,10 +1866,9 @@ export function createRdfWorkerRuntime(postMessage: (message: unknown) => void): } if (payload && Array.isArray(payload.adds)) { - for (const add of payload.adds) { - if (!add) continue; + const rawAdds = (payload.adds as any[]).filter(Boolean).map((a) => deserializeQuad(a, DataFactory)); + for (const quad of skolemizeQuads(rawAdds, DataFactory)) { try { - const quad = deserializeQuad(add, DataFactory); store.addQuad(quad); added += 1; touchedSubjects.add(subjectTermToString(quad.subject)); @@ -2293,18 +2355,16 @@ export function createRdfWorkerRuntime(postMessage: (message: unknown) => void): // No removal step is needed: the reasoner ran on a working copy so the main store // was never touched by string-keyed _addToIndex calls. if (capturedInsertions.length > 0) { + const rawInferred: Quad[] = []; for (const insertion of capturedInsertions) { const subjectTerm = termFromReasonerValue(DataFactory, insertion.subject); const predicateTerm = termFromReasonerValue(DataFactory, insertion.predicate); const objectTerm = termFromReasonerValue(DataFactory, insertion.object); if (!subjectTerm || !predicateTerm || !objectTerm) continue; + rawInferred.push(DataFactory.quad(subjectTerm, predicateTerm, objectTerm, inferredGraphTerm)); + } - const inferredQuad = DataFactory.quad( - subjectTerm, - predicateTerm, - objectTerm, - inferredGraphTerm, - ); + for (const inferredQuad of skolemizeQuads(rawInferred, DataFactory)) { const additionKey = quadKeyFromTerms(inferredQuad); if (!additionSeen.has(additionKey)) { additionSeen.add(additionKey); @@ -2319,7 +2379,7 @@ export function createRdfWorkerRuntime(postMessage: (message: unknown) => void): } } - const subjectValue = subjectTermToString(subjectTerm, subjectTerm.value); + const subjectValue = subjectTermToString(inferredQuad.subject, inferredQuad.subject.value); if (subjectValue) touchedSubjects.add(subjectValue); } } diff --git a/vite-plugin-bookmarklet.ts b/vite-plugin-bookmarklet.ts index 3018093e..e6827af7 100644 --- a/vite-plugin-bookmarklet.ts +++ b/vite-plugin-bookmarklet.ts @@ -20,7 +20,9 @@ export function bookmarkletPlugin(): Plugin { async load(id) { if (id !== RESOLVED_ID) return; - const src = readFileSync(resolve(root, 'public/relay-bookmarklet.js'), 'utf8'); + const filePath = resolve(root, 'public/relay-bookmarklet.js'); + this.addWatchFile(filePath); // invalidate virtual module when source changes + const src = readFileSync(filePath, 'utf8'); const { code } = await transform(src, { minify: true, target: 'es2015', diff --git a/vitest.config.ts b/vitest.config.ts index 444f2ae0..f0bd9174 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -24,6 +24,7 @@ export default defineConfig({ setupFiles: ['src/test-setup.ts'], exclude: [ '.trunk/**', + '.worktrees/**', 'node_modules/**', 'e2e/**', ...(process.env.NETWORK_TESTS ? [] : ['**/*.network.test.ts']),