UNPKG

json-object-editor

Version:

JOE the Json Object Editor | Platform Edition

388 lines (350 loc) 20.6 kB
<!doctype 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>