json-object-editor
Version:
JOE the Json Object Editor | Platform Edition
388 lines (350 loc) • 20.6 kB
HTML
<html>
<head>
<meta charset="utf-8">
<title>JOE Intent Operator</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;margin:20px;background:#f3f4f6;}
h1{margin-top:0;}
.small{font-size:13px;color:#6b7280;margin-bottom:16px; box-sizing: border-box;}
.container{max-width:85%;margin:0 auto;background:#fff;border-radius:12px;box-shadow:0 4px 14px rgba(0,0,0,0.06);padding:16px;}
code{background:#e5e7eb;border-radius:4px;padding:2px 4px;font-size:12px;}
.result-container{display:flex;gap:8px;margin-top:8px;}
.result-json{flex:1;}
.enum-button{display:block;width:100%;padding:6px 8px;background:#3b82f6;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:11px;font-weight:500;margin-bottom:4px;}
.enum-list{font-size:10px;color:#4b5563;line-height:1.4;margin-top:4px;}
.enum-item{margin:2px 0;}
.enum-link{color:#3b82f6;text-decoration:none;}
.enum-link:hover{text-decoration:underline;}
</style>
</head>
<body>
<div id="mcp-nav"></div>
<script src="/JsonObjectEditor/_www/mcp-nav.js"></script>
<div class="container">
<h1>Intent Operator </h1>
<div class="small">
Test the intent-operator plugin which classifies natural language requests and returns structured intent objects.
</div>
<div style="display:flex;gap:8px;margin-bottom:8px;">
<div id="intent-operator-panel" class="small" style="flex:1; background:#f9fafb; border:1px solid #e5e7eb; border-radius:6px; padding:6px;">
<div style="font-weight:600;margin-bottom:4px;">Intent Operator Test (Phase 1)</div>
<div style="margin-bottom:4px;">
<label for="example-requests" style="font-size:11px;color:#6b7280;margin-right:4px;">Example requests:</label>
<select id="example-requests" style="padding:4px;border:1px solid #d1d5db;border-radius:4px;font-size:12px;width:100%;max-width:400px;">
<option value="">-- Select an example --</option>
<option value="add a new task to the project home improvement called fix the sink">[Valid] add a new task to the project home improvement called fix the sink</option>
<option value="add a new tack to the project home called clean my room">[Typo] add a new tack to the project home called clean my room</option>
<option value="add a new projct called website redesign">[Typo] add a new projct called website redesign</option>
<option value="create a new page for the project marketing">[Semantic mismatch] create a new page for the project marketing</option>
<option value="add a new page to the site main website with path /about">[Valid] add a new page to the site main website with path /about</option>
<option value="create a new item called quarterly report">[Ambiguous] create a new item called quarterly report</option>
<option value="create a task in project alpha and link it to page beta">[Multiple] create a task in project alpha and link it to page beta</option>
<option value="update the page status to published">[Update] update the page status to published</option>
<option value="find all tasks in the project engineering">[Search] find all tasks in the project engineering</option>
<option value="add a new taks to the projct home called clean room">[Complex typos] add a new taks to the projct home called clean room</option>
</select>
</div>
<div style="display:flex;gap:8px;margin-bottom:4px;">
<input type="text" id="intent-input" placeholder="Enter natural language command..." style="flex:1;padding:6px;border:1px solid #d1d5db;border-radius:4px;font-size:13px;" />
<button id="intent-submit" style="padding:6px 12px;background:#3b82f6;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:13px;">Get Intent</button>
</div>
<div id="intent-status" style="font-size:11px;color:#6b7280;margin-bottom:4px;"></div>
<div class="result-container">
<pre id="intent-result" class="result-json" style="white-space:pre-wrap;font-size:11px;margin:0;max-height:400px;overflow:auto;background:#fff;border:1px solid #e5e7eb;border-radius:4px;padding:8px;display:none;"></pre>
<div id="clickable-links-section" class="small" style="width:300px; background:#f9fafb; border:1px solid #e5e7eb; border-radius:6px; padding:8px; display:none;">
<div style="font-weight:600;margin-bottom:8px;">Intent Details</div>
<div id="clickable-links-content" style="font-size:11px;">
<div class="enum-section" style="margin-bottom:8px;">
<div style="font-weight:600;margin-bottom:2px;font-size:10px;">Elapsed</div>
<div id="enum-elapsed" style="font-size:10px;color:#4b5563;">-</div>
</div>
<div class="enum-section" style="margin-bottom:8px;">
<div style="font-weight:600;margin-bottom:2px;font-size:10px;">Intent</div>
<div id="enum-intent" style="font-size:10px;color:#4b5563;">-</div>
</div>
<div class="enum-section" style="margin-bottom:8px;">
<div style="font-weight:600;margin-bottom:2px;font-size:10px;">Primary Itemtype</div>
<div id="enum-primary-itemtype" style="font-size:10px;color:#4b5563;">-</div>
</div>
<div class="enum-section" style="margin-bottom:8px;">
<div style="font-weight:600;margin-bottom:2px;font-size:10px;">Operator</div>
<div id="enum-operator" style="font-size:10px;color:#4b5563;">-</div>
</div>
<div class="enum-section" style="margin-bottom:8px;">
<div style="font-weight:600;margin-bottom:2px;font-size:10px;">Confidence</div>
<div id="enum-confidence" style="font-size:10px;color:#4b5563;">-</div>
</div>
<div class="enum-section" style="margin-bottom:8px;">
<div style="font-weight:600;margin-bottom:2px;font-size:10px;">Needs Clarification</div>
<div id="enum-clarification" style="font-size:10px;color:#4b5563;">-</div>
</div>
<div class="enum-section" style="margin-bottom:8px;">
<button class="enum-button" id="schemas-button" style="display:none;" onclick="window.open(document.getElementById('schemas-url').href, '_blank')">Schemas to Load</button>
<a id="schemas-url" href="#" style="display:none;"></a>
<div id="schemas-list" class="enum-list" style="display:none;"></div>
</div>
<div class="enum-section" style="margin-bottom:8px;">
<div id="objects-section" style="display:none;">
<div style="font-weight:600;margin-bottom:4px;font-size:10px;">Objects to Resolve</div>
<div id="objects-list" class="enum-list"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="intent-enums-panel" class="small" style="width:300px; background:#f9fafb; border:1px solid #e5e7eb; border-radius:6px; padding:6px;">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px;">
<div style="font-weight:600;">Intents</div>
<a href="/API/plugin/intent-operator/intents" target="intent-operator-intents" style="font-size:10px;color:#3b82f6;text-decoration:none;">View API →</a>
</div>
<div id="intents-accordion" style="font-size:11px;">
<div style="color:#6b7280;font-size:10px;margin-bottom:4px;">Loading intents...</div>
</div>
</div>
</div>
</div>
<script>
(function(){
function $(id){ return document.getElementById(id); }
// Wait for DOM to be ready
function init() {
try {
// Example requests dropdown
const dropdown = $('example-requests');
const input = $('intent-input');
if (dropdown && input) {
dropdown.addEventListener('change', function() {
if (dropdown.value) {
input.value = dropdown.value;
dropdown.selectedIndex = 0; // Reset to first option
}
});
} else {
console.error('[intent-operator] Missing elements: dropdown=' + !!dropdown + ', input=' + !!input);
}
// Intent operator test
const submit = $('intent-submit');
const status = $('intent-status');
const result = $('intent-result');
if (input && submit && status && result) {
async function getIntent() {
const text = input.value.trim();
if (!text) {
status.textContent = 'Please enter some text.';
return;
}
status.textContent = 'Calling intent-operator...';
result.style.display = 'none';
const linksSection = $('clickable-links-section');
if (linksSection) linksSection.style.display = 'none';
submit.disabled = true;
try {
const resp = await fetch('/API/plugin/intent-operator', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ text: text })
});
const data = await resp.json();
status.textContent = resp.ok ? 'Intent classified successfully.' : 'Error: ' + (data.errors && data.errors.join(', ') || 'Unknown error');
result.textContent = JSON.stringify(data, null, 2);
result.style.display = 'block';
// Populate clickable links section
if (resp.ok && linksSection) {
// Elapsed
const elapsedEl = $('enum-elapsed');
if (elapsedEl) elapsedEl.textContent = (data.elapsed || 0).toFixed(3) + 's';
// Intent
const intentEl = $('enum-intent');
if (intentEl && data.intent) {
intentEl.textContent = data.intent.intent || '-';
}
// Primary Itemtype
const primaryEl = $('enum-primary-itemtype');
if (primaryEl && data.intent) {
primaryEl.textContent = data.intent.primary_itemtype || '-';
}
// Operator
const operatorEl = $('enum-operator');
if (operatorEl && data.intent) {
operatorEl.textContent = data.intent.operator || '-';
}
// Confidence
const confidenceEl = $('enum-confidence');
if (confidenceEl && data.intent) {
const conf = data.intent.confidence || 0;
confidenceEl.textContent = (conf * 100).toFixed(0) + '%';
}
// Needs Clarification
const clarificationEl = $('enum-clarification');
if (clarificationEl && data.intent) {
clarificationEl.textContent = data.intent.needs_clarification ? 'Yes' : 'No';
clarificationEl.style.color = data.intent.needs_clarification ? '#ef4444' : '#10b981';
}
// Schemas to Load - single combined request
const schemasButton = $('schemas-button');
const schemasUrl = $('schemas-url');
const schemasList = $('schemas-list');
if (schemasButton && schemasUrl && schemasList) {
if (data.schemas_to_load && data.schemas_to_load.length > 0) {
const schemas = data.schemas_to_load;
// Build single URL with comma-separated schemas
const schemaNames = schemas.map(function(s) { return encodeURIComponent(s); }).join(',');
const url = '/API/schema/' + schemaNames;
schemasUrl.href = url;
// Show comma-delimited list below button
schemasList.textContent = schemas.join(', ');
schemasButton.style.display = 'block';
schemasList.style.display = 'block';
} else {
schemasButton.style.display = 'none';
schemasList.style.display = 'none';
}
}
// Objects to Resolve (multiple individual links with proper itemtype filtering)
const objectsSection = $('objects-section');
const objectsList = $('objects-list');
if (objectsSection && objectsList) {
if (data.objects_to_resolve && data.objects_to_resolve.length > 0) {
const objects = data.objects_to_resolve;
const objectLinks = objects.map(function(obj) {
const itemtype = obj.itemtype || '';
const name = obj.name || '';
// Build URL with itemtype in query string (only if itemtype exists)
const params = new URLSearchParams();
params.set('mode', 'fuzzy');
params.set('q', name);
if (itemtype) {
params.set('itemtype', itemtype);
}
const url = '/API/search?' + params.toString();
const label = (itemtype ? itemtype + ': ' : '') + name;
return '<div class="enum-item"><a href="' + url + '" target="_blank" class="enum-link">' + label + '</a></div>';
}).join('');
objectsList.innerHTML = objectLinks;
objectsSection.style.display = 'block';
} else {
objectsSection.style.display = 'none';
}
}
linksSection.style.display = 'block';
}
} catch (e) {
status.textContent = 'Error: ' + e.message;
result.textContent = String(e);
result.style.display = 'block';
} finally {
submit.disabled = false;
}
}
submit.addEventListener('click', getIntent);
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
getIntent();
}
});
} else {
console.error('[intent-operator] Missing elements: input=' + !!input + ', submit=' + !!submit + ', status=' + !!status + ', result=' + !!result);
}
// Load and render intents accordion
const accordion = document.getElementById('intents-accordion');
if (accordion) {
async function loadIntents() {
try {
const resp = await fetch('/API/plugin/intent-operator/intents', {
credentials: 'include'
});
const data = await resp.json();
if (!data || !data.intents || !Array.isArray(data.intents)) {
accordion.innerHTML = '<div style="color:#ef4444;font-size:10px;">Failed to load intents</div>';
return;
}
const intents = data.intents;
let html = '';
intents.forEach(function(intent, index) {
const isCustom = intent.overwrite === true || (index >= data.defaults_count && intent.overwrite !== false);
const intentId = 'intent-' + index;
html += '<div style="margin-bottom:4px;border:1px solid #e5e7eb;border-radius:4px;overflow:hidden;">';
html += '<input type="checkbox" id="' + intentId + '" style="display:none;">';
html += '<label for="' + intentId + '" style="display:block;padding:4px 6px;background:#fff;cursor:pointer;font-weight:500;font-size:11px;color:#374151;user-select:none;">';
html += intent.name + (isCustom ? ' <span style="color:#6b7280;font-weight:400;">(custom)</span>' : '');
html += ' <span class="intent-arrow" style="float:right;color:#9ca3af;">▼</span>';
html += '</label>';
html += '<div class="intent-content" style="padding:6px;background:#fff;font-size:10px;color:#4b5563;display:none;">';
// Description
if (intent.description) {
html += '<div style="margin-bottom:4px;"><strong>Description:</strong> ' + (intent.description || '') + '</div>';
}
// Handoff
if (intent.handoff) {
html += '<div style="margin-bottom:4px;"><strong>Handoff:</strong> <code style="background:#f3f4f6;padding:1px 4px;border-radius:2px;">' + intent.handoff + '</code></div>';
}
// Examples
if (intent.examples && Array.isArray(intent.examples) && intent.examples.length > 0) {
html += '<div style="margin-bottom:4px;"><strong>Examples:</strong><ul style="margin:2px 0 0 16px;padding:0;">';
intent.examples.forEach(function(ex) {
html += '<li style="margin:1px 0;">"' + ex + '"</li>';
});
html += '</ul></div>';
}
// Operators
if (intent.operators && Array.isArray(intent.operators) && intent.operators.length > 0) {
html += '<div style="margin-bottom:4px;"><strong>Operators:</strong> ' + intent.operators.join(', ') + '</div>';
}
// Required information
if (intent.required_information && Array.isArray(intent.required_information) && intent.required_information.length > 0) {
html += '<div style="margin-bottom:4px;"><strong>Required Information:</strong> ' + intent.required_information.join(', ') + '</div>';
} else if (intent.required_information !== undefined) {
html += '<div style="margin-bottom:4px;"><strong>Required Information:</strong> <em style="color:#9ca3af;">none</em></div>';
}
html += '</div>';
html += '</div>';
});
accordion.innerHTML = html;
// Wire up accordion behavior
intents.forEach(function(intent, index) {
const checkbox = document.getElementById('intent-' + index);
if (!checkbox) return;
const label = checkbox.nextElementSibling;
const content = label ? label.nextElementSibling : null;
const arrow = label ? label.querySelector('.intent-arrow') : null;
if (checkbox && label && content) {
checkbox.addEventListener('change', function() {
content.style.display = checkbox.checked ? 'block' : 'none';
if (arrow) {
arrow.textContent = checkbox.checked ? '▲' : '▼';
}
});
// Initialize display state (all collapsed)
content.style.display = 'none';
if (arrow) {
arrow.textContent = '▼';
}
}
});
} catch (e) {
console.error('[intent-operator] error loading intents', e);
accordion.innerHTML = '<div style="color:#ef4444;font-size:10px;">Error loading intents: ' + e.message + '</div>';
}
}
loadIntents();
}
} catch (e) {
console.error('[intent-operator] Error in init:', e);
throw e;
}
}
// Wait for DOMContentLoaded, or call immediately if already loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init(); // DOM is already loaded, elements should be available
}
})();
</script>
</body>
</html>