Add HTTP interface, templates, generate_from_template, unified Dockerfile
Continuous Integration / Test Suite (macos-latest, nightly) (push) Has been cancelled
Continuous Integration / Test Suite (macos-latest, stable) (push) Has been cancelled
Continuous Integration / Test Suite (ubuntu-latest, 1.70.0) (push) Has been cancelled
Continuous Integration / Test Suite (ubuntu-latest, beta) (push) Has been cancelled
Continuous Integration / Test Suite (ubuntu-latest, nightly) (push) Has been cancelled
Continuous Integration / Test Suite (ubuntu-latest, stable) (push) Has been cancelled
Continuous Integration / Test Suite (windows-latest, stable) (push) Has been cancelled
Continuous Integration / Security Audit (push) Has been cancelled
Continuous Integration / Code Coverage (push) Has been cancelled
Continuous Integration / Performance Benchmarks (push) Has been cancelled
Continuous Integration / Memory Safety Check (push) Has been cancelled
Continuous Integration / Docker Build Test (push) Has been cancelled
Continuous Integration / Release Readiness (push) Has been cancelled
Continuous Integration / Integration Tests (push) Has been cancelled
Continuous Integration / Stress Testing (push) Has been cancelled
Continuous Integration / Notify Results (push) Has been cancelled
Continuous Integration / Test Suite (macos-latest, nightly) (push) Has been cancelled
Continuous Integration / Test Suite (macos-latest, stable) (push) Has been cancelled
Continuous Integration / Test Suite (ubuntu-latest, 1.70.0) (push) Has been cancelled
Continuous Integration / Test Suite (ubuntu-latest, beta) (push) Has been cancelled
Continuous Integration / Test Suite (ubuntu-latest, nightly) (push) Has been cancelled
Continuous Integration / Test Suite (ubuntu-latest, stable) (push) Has been cancelled
Continuous Integration / Test Suite (windows-latest, stable) (push) Has been cancelled
Continuous Integration / Security Audit (push) Has been cancelled
Continuous Integration / Code Coverage (push) Has been cancelled
Continuous Integration / Performance Benchmarks (push) Has been cancelled
Continuous Integration / Memory Safety Check (push) Has been cancelled
Continuous Integration / Docker Build Test (push) Has been cancelled
Continuous Integration / Release Readiness (push) Has been cancelled
Continuous Integration / Integration Tests (push) Has been cancelled
Continuous Integration / Stress Testing (push) Has been cancelled
Continuous Integration / Notify Results (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,553 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DOCX MCP Server - Web Interface</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #f5f5f5;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: #1a73e8;
|
||||
color: white;
|
||||
padding: 1rem 2rem;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 0.875rem;
|
||||
opacity: 0.9;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.panel {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.panel h2 {
|
||||
font-size: 1.25rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #1a73e8;
|
||||
}
|
||||
|
||||
.tool-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.tool-card {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
padding: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.tool-card:hover {
|
||||
border-color: #1a73e8;
|
||||
box-shadow: 0 2px 8px rgba(26, 115, 232, 0.2);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.tool-card h3 {
|
||||
font-size: 1rem;
|
||||
color: #1a73e8;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.tool-card p {
|
||||
font-size: 0.875rem;
|
||||
color: #666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.25rem;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group textarea,
|
||||
.form-group select {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.form-group textarea {
|
||||
min-height: 200px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.5rem 1rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #1a73e8;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: #1557b0;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: #f1f1f1;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
.response-panel {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
margin-top: 1rem;
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.response-panel pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
font-family: monospace;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.status {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status.success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status.error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.status.loading {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.connection-status {
|
||||
position: fixed;
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
font-size: 0.875rem;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.connection-status.connected {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.connection-status.disconnected {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>DOCX MCP Server</h1>
|
||||
<p>Word Document Processing Interface</p>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="panel">
|
||||
<h2>Templates</h2>
|
||||
<div id="templatesPanel">
|
||||
<p>Loading templates...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<h2>Available Tools</h2>
|
||||
<div class="tool-grid" id="toolGrid">
|
||||
<div style="text-align: center; padding: 2rem;">
|
||||
<p>Loading tools...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel" id="toolFormPanel" style="display: none;">
|
||||
<h2 id="toolName">Tool Name</h2>
|
||||
<p id="toolDescription" style="margin-bottom: 1rem; color: #666;"></p>
|
||||
|
||||
<div id="toolForm">
|
||||
<!-- Form fields will be generated here -->
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 1rem;">
|
||||
<button class="btn btn-primary" onclick="executeTool()">Execute</button>
|
||||
<button class="btn btn-secondary" onclick="resetForm()">Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel" id="responsePanel" style="display: none;">
|
||||
<h2>Response</h2>
|
||||
<div id="responseStatus"></div>
|
||||
<div class="response-panel">
|
||||
<pre id="responseContent"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="connection-status" id="connectionStatus">
|
||||
Connecting...
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let currentTool = null;
|
||||
let tools = [];
|
||||
let ws = null;
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadTools();
|
||||
loadTemplates();
|
||||
connectWebSocket();
|
||||
});
|
||||
|
||||
// Load available templates
|
||||
async function loadTemplates() {
|
||||
const container = document.getElementById('templatesPanel');
|
||||
try {
|
||||
const response = await fetch('/api/call', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: 'list_templates', arguments: {} })
|
||||
});
|
||||
const data = await response.json();
|
||||
if (!data.success || !data.content || !data.content.templates || data.content.templates.length === 0) {
|
||||
container.innerHTML = '<p>No templates available.</p>';
|
||||
return;
|
||||
}
|
||||
const list = document.createElement('div');
|
||||
list.style.display = 'flex';
|
||||
list.style.flexWrap = 'wrap';
|
||||
list.style.gap = '0.5rem';
|
||||
data.content.templates.forEach(t => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'btn btn-secondary';
|
||||
btn.textContent = t;
|
||||
btn.onclick = () => openTemplate(t);
|
||||
list.appendChild(btn);
|
||||
});
|
||||
container.appendChild(list);
|
||||
} catch (err) {
|
||||
container.innerHTML = '<p>Failed to load templates.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// Open a template via the server
|
||||
async function openTemplate(name) {
|
||||
const responsePanel = document.getElementById('responsePanel');
|
||||
const status = document.getElementById('responseStatus');
|
||||
const content = document.getElementById('responseContent');
|
||||
responsePanel.style.display = 'block';
|
||||
status.innerHTML = '<span class="status loading">Opening template...</span>';
|
||||
content.textContent = '';
|
||||
try {
|
||||
const res = await fetch('/api/call', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: 'open_template', arguments: { name } })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
status.innerHTML = '<span class="status success">Template opened</span>';
|
||||
content.textContent = JSON.stringify(data.content, null, 2);
|
||||
} else {
|
||||
status.innerHTML = '<span class="status error">Error</span>';
|
||||
content.textContent = data.error || JSON.stringify(data, null, 2);
|
||||
}
|
||||
} catch (err) {
|
||||
status.innerHTML = '<span class="status error">Error</span>';
|
||||
content.textContent = err.message;
|
||||
}
|
||||
}
|
||||
|
||||
// Load available tools
|
||||
async function loadTools() {
|
||||
try {
|
||||
const response = await fetch('/api/tools');
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
tools = data.tools;
|
||||
renderToolGrid();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load tools:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Render tool cards
|
||||
function renderToolGrid() {
|
||||
const grid = document.getElementById('toolGrid');
|
||||
grid.innerHTML = '';
|
||||
|
||||
tools.forEach(tool => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'tool-card';
|
||||
card.onclick = () => selectTool(tool);
|
||||
|
||||
card.innerHTML = `
|
||||
<h3>${tool.name}</h3>
|
||||
<p>${tool.description || 'No description available'}</p>
|
||||
`;
|
||||
|
||||
grid.appendChild(card);
|
||||
});
|
||||
}
|
||||
|
||||
// Select a tool to use
|
||||
function selectTool(tool) {
|
||||
currentTool = tool;
|
||||
|
||||
document.getElementById('toolName').textContent = tool.name;
|
||||
document.getElementById('toolDescription').textContent = tool.description;
|
||||
|
||||
// Generate form based on input schema
|
||||
generateForm(tool.input_schema);
|
||||
|
||||
document.getElementById('toolFormPanel').style.display = 'block';
|
||||
document.getElementById('responsePanel').style.display = 'none';
|
||||
}
|
||||
|
||||
// Generate form fields from schema
|
||||
function generateForm(schema) {
|
||||
const form = document.getElementById('toolForm');
|
||||
form.innerHTML = '';
|
||||
|
||||
if (!schema.properties) return;
|
||||
|
||||
Object.entries(schema.properties).forEach(([name, prop]) => {
|
||||
const group = document.createElement('div');
|
||||
group.className = 'form-group';
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.textContent = `${name}${schema.required && schema.required.includes(name) ? ' *' : ''}`;
|
||||
|
||||
let input;
|
||||
switch (prop.type) {
|
||||
case 'string':
|
||||
if (prop.enum) {
|
||||
input = document.createElement('select');
|
||||
input.id = `field_${name}`;
|
||||
input.innerHTML = '<option value="">Select...</option>';
|
||||
prop.enum.forEach(option => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = option;
|
||||
opt.textContent = option;
|
||||
input.appendChild(opt);
|
||||
});
|
||||
} else {
|
||||
input = document.createElement('textarea');
|
||||
input.id = `field_${name}`;
|
||||
input.placeholder = prop.description || `Enter ${name}`;
|
||||
}
|
||||
break;
|
||||
case 'boolean':
|
||||
input = document.createElement('input');
|
||||
input.type = 'checkbox';
|
||||
input.id = `field_${name}`;
|
||||
break;
|
||||
case 'number':
|
||||
case 'integer':
|
||||
input = document.createElement('input');
|
||||
input.type = 'number';
|
||||
input.id = `field_${name}`;
|
||||
input.placeholder = prop.description || `Enter ${name}`;
|
||||
break;
|
||||
case 'array':
|
||||
case 'object':
|
||||
input = document.createElement('textarea');
|
||||
input.id = `field_${name}`;
|
||||
input.placeholder = prop.description || `Enter JSON for ${name}`;
|
||||
input.style.fontFamily = 'monospace';
|
||||
break;
|
||||
default:
|
||||
input = document.createElement('input');
|
||||
input.id = `field_${name}`;
|
||||
input.placeholder = prop.description || `Enter ${name}`;
|
||||
}
|
||||
|
||||
group.appendChild(label);
|
||||
group.appendChild(input);
|
||||
form.appendChild(group);
|
||||
});
|
||||
}
|
||||
|
||||
// Execute tool call
|
||||
async function executeTool() {
|
||||
if (!currentTool) return;
|
||||
|
||||
const status = document.getElementById('responseStatus');
|
||||
const content = document.getElementById('responseContent');
|
||||
|
||||
status.innerHTML = '<span class="status loading">Executing...</span>';
|
||||
content.textContent = '';
|
||||
document.getElementById('responsePanel').style.display = 'block';
|
||||
|
||||
// Collect form data
|
||||
const arguments = {};
|
||||
const schema = currentTool.input_schema;
|
||||
|
||||
if (schema.properties) {
|
||||
Object.entries(schema.properties).forEach(([name, prop]) => {
|
||||
const field = document.getElementById(`field_${name}`);
|
||||
if (field) {
|
||||
let value;
|
||||
switch (prop.type) {
|
||||
case 'boolean':
|
||||
value = field.checked;
|
||||
break;
|
||||
case 'number':
|
||||
case 'integer':
|
||||
value = parseInt(field.value) || field.value;
|
||||
break;
|
||||
case 'array':
|
||||
case 'object':
|
||||
try {
|
||||
value = JSON.parse(field.value);
|
||||
} catch {
|
||||
value = field.value;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
value = field.value;
|
||||
}
|
||||
if (value || value === false) {
|
||||
arguments[name] = value;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/call', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: currentTool.name,
|
||||
arguments: arguments
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
status.innerHTML = '<span class="status success">Success</span>';
|
||||
content.textContent = JSON.stringify(data.content, null, 2);
|
||||
} else {
|
||||
status.innerHTML = '<span class="status error">Error</span>';
|
||||
content.textContent = data.error || 'Unknown error occurred';
|
||||
}
|
||||
} catch (error) {
|
||||
status.innerHTML = '<span class="status error">Error</span>';
|
||||
content.textContent = error.message;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset form
|
||||
function resetForm() {
|
||||
const fields = document.querySelectorAll('#toolForm input, #toolForm textarea, #toolForm select');
|
||||
fields.forEach(field => {
|
||||
if (field.type === 'checkbox') {
|
||||
field.checked = false;
|
||||
} else {
|
||||
field.value = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// WebSocket connection for real-time updates
|
||||
function connectWebSocket() {
|
||||
const status = document.getElementById('connectionStatus');
|
||||
|
||||
try {
|
||||
ws = new WebSocket(`ws://${window.location.host}/ws`);
|
||||
|
||||
ws.onopen = () => {
|
||||
status.textContent = 'Connected';
|
||||
status.className = 'connection-status connected';
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
status.textContent = 'Disconnected';
|
||||
status.className = 'connection-status disconnected';
|
||||
setTimeout(connectWebSocket, 5000);
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
status.textContent = 'Connection Error';
|
||||
status.className = 'connection-status disconnected';
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to connect WebSocket:', error);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user