UNPKG

@vectorchat/mcp-server

Version:

VectorChat MCP Server - Encrypted AI-to-AI communication with hardware security (YubiKey/TPM). 45+ MCP tools for Windsurf, Claude, and AI assistants. Model-based identity with EMDM encryption. Dynamic AI playbook system, communication zones, message relay

769 lines (662 loc) 27.4 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Identity Setup - VectorChat Web</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } :root { --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%); --primary-color: #667eea; --secondary-color: #764ba2; --success-color: #4caf50; --warning-color: #ff9800; --error-color: #f44336; --surface-color: #ffffff; --background-color: #f5f5f5; --text-primary: #333333; --text-secondary: #666666; --border-color: #e0e0e0; --shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: var(--background-color); color: var(--text-primary); line-height: 1.6; padding: 20px; } .container { max-width: 600px; margin: 0 auto; background: var(--surface-color); border-radius: 12px; box-shadow: var(--shadow); overflow: hidden; } .header { background: var(--primary-gradient); color: white; padding: 24px; text-align: center; } .header h1 { font-size: 1.8rem; font-weight: 600; margin-bottom: 8px; } .header p { opacity: 0.9; font-size: 1rem; } .progress-bar { background: rgba(255, 255, 255, 0.2); border-radius: 20px; height: 6px; margin: 20px 0; overflow: hidden; } .progress-fill { height: 100%; background: white; border-radius: 20px; transition: width 0.3s ease; width: 0%; } .content { padding: 24px; } .form-group { margin-bottom: 24px; } .form-group:last-child { margin-bottom: 0; } .form-label { display: block; font-weight: 600; margin-bottom: 8px; color: var(--text-primary); } .form-input { width: 100%; padding: 12px 16px; border: 2px solid var(--border-color); border-radius: 8px; font-size: 1rem; transition: border-color 0.3s ease; font-family: inherit; } .form-input:focus { outline: none; border-color: var(--primary-color); box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } .form-input.error { border-color: var(--error-color); } .form-help { font-size: 0.85rem; color: var(--text-secondary); margin-top: 4px; } .form-error { font-size: 0.85rem; color: var(--error-color); margin-top: 4px; } .model-info { background: #e8f5e8; border: 1px solid #4caf50; border-radius: 8px; padding: 16px; margin-bottom: 20px; } .model-info h3 { color: #2e7d32; margin-bottom: 8px; font-size: 1rem; } .model-info p { color: #4caf50; font-size: 0.9rem; margin: 0; } .security-info { background: #fff3e0; border: 1px solid #ff9800; border-radius: 8px; padding: 16px; margin-bottom: 20px; } .security-info h3 { color: #e65100; margin-bottom: 8px; font-size: 1rem; } .security-info p { color: #e65100; font-size: 0.9rem; margin: 0; } .preview-section { background: #f5f5f5; border: 1px solid var(--border-color); border-radius: 8px; padding: 16px; margin-bottom: 20px; } .preview-title { font-weight: 600; margin-bottom: 12px; color: var(--text-primary); } .identity-preview { font-family: 'Courier New', monospace; font-size: 0.9rem; background: white; padding: 12px; border-radius: 6px; border: 1px solid #e0e0e0; word-break: break-all; } .actions { padding: 24px; border-top: 1px solid var(--border-color); display: flex; gap: 12px; justify-content: space-between; } .btn { padding: 12px 24px; border: none; border-radius: 8px; cursor: pointer; font-weight: 500; transition: all 0.3s ease; font-size: 1rem; display: flex; align-items: center; gap: 8px; } .btn-primary { background: var(--primary-gradient); color: white; } .btn-primary:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3); } .btn-secondary { background: #f5f5f5; color: var(--text-primary); border: 1px solid var(--border-color); } .btn-secondary:hover { background: #e8e8e8; } .btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none !important; } .loading { text-align: center; padding: 40px; color: var(--text-secondary); } .loading-spinner { width: 32px; height: 32px; border: 3px solid var(--border-color); border-top: 3px solid var(--primary-color); border-radius: 50%; animation: spin 1s linear infinite; margin: 0 auto 16px; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .success-message { background: #e8f5e8; border: 1px solid #4caf50; border-radius: 8px; padding: 20px; text-align: center; margin-bottom: 20px; } .success-message h3 { color: #2e7d32; margin-bottom: 8px; } .success-message p { color: #4caf50; } @media (max-width: 768px) { .container { margin: 0; border-radius: 0; height: 100vh; } .actions { flex-direction: column; } body { padding: 0; } } </style> </head> <body> <div class="container"> <div class="header"> <h1>🔐 Setup Your Identity</h1> <p>Create your secure VectorChat identity</p> <div class="progress-bar"> <div class="progress-fill" id="progressFill"></div> </div> </div> <div class="content"> <!-- Model Information --> <div class="model-info" id="modelInfo" style="display: none;"> <h3>🤖 AI Model Selected</h3> <p id="modelDetails">Loading model information...</p> </div> <!-- Security Information --> <div class="security-info"> <h3>🛡️ Security & Privacy</h3> <p>Your identity will be cryptographically linked to your AI model using EMDM encryption with a 496T key space. This ensures maximum security and privacy for all communications.</p> </div> <!-- Identity Form --> <form id="identityForm"> <div class="form-group"> <label class="form-label" for="displayName">Display Name</label> <input type="text" class="form-input" id="displayName" placeholder="e.g., Alice, Bob, or your preferred name" required> <div class="form-help">This is how you'll appear to other users</div> </div> <div class="form-group"> <label class="form-label" for="username">Username</label> <input type="text" class="form-input" id="username" placeholder="e.g., alice, bob123" required> <div class="form-help">Unique identifier (lowercase, no spaces)</div> <div class="form-error" id="usernameError" style="display: none;"></div> </div> <div class="form-group"> <label class="form-label" for="modelPath">AI Model</label> <select class="form-input" id="modelPath" required> <option value="">Select your AI model...</option> </select> <div class="form-help">The model will be cryptographically verified for security</div> </div> <div class="form-group"> <label class="form-label" for="contextSize">Context Size</label> <select class="form-input" id="contextSize" required> <option value="2048">2K tokens (Fast, recommended)</option> <option value="4096">4K tokens (Balanced)</option> <option value="8192">8K tokens (Large context)</option> <option value="16384">16K tokens (Maximum)</option> </select> <div class="form-help">How much conversation history the AI remembers</div> </div> <div class="form-group"> <label class="form-label">Advanced Options</label> <div style="display: flex; gap: 16px; flex-wrap: wrap;"> <label style="display: flex; align-items: center; gap: 8px; font-weight: normal;"> <input type="checkbox" id="enableIPFS" checked> <span>Enable IPFS P2P networking</span> </label> <label style="display: flex; align-items: center; gap: 8px; font-weight: normal;"> <input type="checkbox" id="autoDownload" checked> <span>Auto-download missing models</span> </label> </div> <div class="form-help">IPFS enables decentralized peer-to-peer communication</div> </div> </form> <!-- Identity Preview --> <div class="preview-section"> <div class="preview-title">🔍 Identity Preview</div> <div class="identity-preview" id="identityPreview"> Generating preview... </div> </div> <!-- Success Message --> <div class="success-message" id="successMessage" style="display: none;"> <h3>✅ Identity Created Successfully!</h3> <p>Your VectorChat identity has been configured and is ready to use.</p> </div> <div class="loading" id="loadingIndicator" style="display: none;"> <div class="loading-spinner"></div> <p>Creating your identity...</p> </div> </div> <div class="actions"> <button class="btn btn-secondary" onclick="goBack()">← Back</button> <div> <button class="btn btn-secondary" onclick="refreshPreview()">🔄 Preview</button> <button class="btn btn-primary" onclick="createIdentity()" id="createButton"> Create Identity → </button> </div> </div> </div> <script> let selectedModel = null; let currentStep = 1; const totalSteps = 3; // Initialize document.addEventListener('DOMContentLoaded', function() { loadModelOptions(); setupEventListeners(); updateProgress(); updatePreview(); // Check if model was passed from URL or parent window const urlParams = new URLSearchParams(window.location.search); const modelParam = urlParams.get('model'); if (modelParam) { selectedModel = JSON.parse(decodeURIComponent(modelParam)); updateModelInfo(); } // Listen for model selection from parent window.addEventListener('message', function(event) { if (event.data.type === 'model_selected') { selectedModel = event.data.model; updateModelInfo(); } }); }); function setupEventListeners() { // Update preview on input changes ['displayName', 'username', 'modelPath', 'contextSize'].forEach(id => { document.getElementById(id).addEventListener('input', updatePreview); document.getElementById(id).addEventListener('change', updatePreview); }); // Username validation document.getElementById('username').addEventListener('input', validateUsername); } async function loadModelOptions() { try { const response = await fetch('http://localhost:3737/api/scan-models', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ paths: ['~/.vectorchat/models', '~/.lmstudio/models', '~/.ollama/models'] }) }); const data = await response.json(); const modelSelect = document.getElementById('modelPath'); // Clear existing options modelSelect.innerHTML = '<option value="">Select your AI model...</option>'; // Add available models if (data.models && data.models.length > 0) { data.models.forEach(model => { const option = document.createElement('option'); option.value = model.path; option.textContent = `${model.name} (${model.size} GB)`; modelSelect.appendChild(option); }); } else { // Add fallback options const fallbackOptions = [ { name: 'Qwen 3 1.7B (Recommended)', path: 'qwen3-1.7b' }, { name: 'Qwen 2.5 7B', path: 'qwen2.5-7b' }, { name: 'GPT-2 (Fallback)', path: 'gpt2' } ]; fallbackOptions.forEach(model => { const option = document.createElement('option'); option.value = model.path; option.textContent = model.name; modelSelect.appendChild(option); }); } } catch (error) { console.error('Error loading models:', error); // Show error but continue with fallback options const modelSelect = document.getElementById('modelPath'); modelSelect.innerHTML = ` <option value="">Select your AI model...</option> <option value="qwen3-1.7b">Qwen 3 1.7B (Recommended)</option> <option value="qwen2.5-7b">Qwen 2.5 7B</option> <option value="gpt2">GPT-2 (Fallback)</option> `; } } function validateUsername() { const username = document.getElementById('username').value; const errorElement = document.getElementById('usernameError'); if (username.length > 0) { if (username.length < 3) { errorElement.textContent = 'Username must be at least 3 characters'; errorElement.style.display = 'block'; document.getElementById('username').classList.add('error'); return false; } if (!/^[a-z0-9_-]+$/.test(username)) { errorElement.textContent = 'Username can only contain lowercase letters, numbers, hyphens, and underscores'; errorElement.style.display = 'block'; document.getElementById('username').classList.add('error'); return false; } errorElement.style.display = 'none'; document.getElementById('username').classList.remove('error'); return true; } errorElement.style.display = 'none'; document.getElementById('username').classList.remove('error'); return true; } function updatePreview() { const displayName = document.getElementById('displayName').value; const username = document.getElementById('username').value; const modelPath = document.getElementById('modelPath').value; const contextSize = document.getElementById('contextSize').value; let preview = `Display Name: ${displayName || 'Not set'}\n`; preview += `Username: @${username || 'not-set'}\n`; preview += `Model: ${getModelName(modelPath)}\n`; preview += `Context: ${contextSize} tokens\n`; preview += `IPFS: ${document.getElementById('enableIPFS').checked ? 'Enabled' : 'Disabled'}\n`; preview += `Auto-download: ${document.getElementById('autoDownload').checked ? 'Enabled' : 'Disabled'}\n\n`; preview += `Identity Hash: ${generateIdentityHash(displayName, username, modelPath)}`; document.getElementById('identityPreview').textContent = preview; updateProgress(); } function getModelName(path) { if (!path) return 'Not selected'; const modelNames = { 'qwen3-1.7b': 'Qwen 3 1.7B', 'qwen2.5-7b': 'Qwen 2.5 7B', 'gpt2': 'GPT-2' }; return modelNames[path] || path.split('/').pop() || 'Unknown'; } function generateIdentityHash(displayName, username, modelPath) { // Simple hash generation for preview const input = `${displayName}-${username}-${modelPath}-${Date.now()}`; let hash = 0; for (let i = 0; i < input.length; i++) { const char = input.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; // Convert to 32-bit integer } return Math.abs(hash).toString(36).substring(0, 12).toUpperCase(); } function updateModelInfo() { if (selectedModel) { document.getElementById('modelInfo').style.display = 'block'; document.getElementById('modelDetails').textContent = `${selectedModel.name} - ${selectedModel.description}`; document.getElementById('modelPath').value = selectedModel.path || selectedModel.id; updatePreview(); } } function updateProgress() { const displayName = document.getElementById('displayName').value; const username = document.getElementById('username').value; const modelPath = document.getElementById('modelPath').value; let completedSteps = 0; if (displayName) completedSteps++; if (username && validateUsername()) completedSteps++; if (modelPath) completedSteps++; currentStep = completedSteps + 1; const progress = (completedSteps / totalSteps) * 100; document.getElementById('progressFill').style.width = `${progress}%`; } async function createIdentity() { // Validate form if (!validateForm()) { showNotification('Please fill in all required fields correctly', 'error'); return; } showLoading(); try { const identityData = { displayName: document.getElementById('displayName').value, username: document.getElementById('username').value, modelPath: document.getElementById('modelPath').value, contextSize: parseInt(document.getElementById('contextSize').value), enableIPFS: document.getElementById('enableIPFS').checked, autoDownload: document.getElementById('autoDownload').checked, userIdentity: `${document.getElementById('username').value}@[identity]` }; // Send to daemon const response = await fetch('http://localhost:3737/api/apply-config', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ aiName: identityData.displayName, userName: identityData.username, modelType: identityData.modelPath, contextSize: identityData.contextSize, enableIPFS: identityData.enableIPFS, mcpPort: 8766, wsPort: 8765 }) }); if (response.ok) { showSuccess(); showNotification('Identity created successfully!', 'success'); } else { throw new Error(`HTTP ${response.status}`); } } catch (error) { console.error('Error creating identity:', error); hideLoading(); showNotification(`Failed to create identity: ${error.message}`, 'error'); } } function validateForm() { const displayName = document.getElementById('displayName').value; const username = document.getElementById('username').value; const modelPath = document.getElementById('modelPath').value; return displayName && username && validateUsername() && modelPath; } function showSuccess() { hideLoading(); document.getElementById('successMessage').style.display = 'block'; document.getElementById('identityForm').style.display = 'none'; document.querySelector('.actions').style.display = 'none'; // Update header document.querySelector('.header h1').textContent = '✅ Identity Created!'; document.querySelector('.header p').textContent = 'Your VectorChat identity is ready to use'; // Add continue button setTimeout(() => { const actions = document.querySelector('.actions'); actions.style.display = 'flex'; actions.innerHTML = ` <button class="btn btn-secondary" onclick="goBack()">← Setup Another</button> <button class="btn btn-primary" onclick="continueToApp()">Continue to App →</button> `; }, 2000); } function continueToApp() { // Save identity to localStorage const identityData = { displayName: document.getElementById('displayName').value, username: document.getElementById('username').value, modelPath: document.getElementById('modelPath').value, contextSize: document.getElementById('contextSize').value, enableIPFS: document.getElementById('enableIPFS').checked, autoDownload: document.getElementById('autoDownload').checked }; localStorage.setItem('vectorchat-identity', JSON.stringify(identityData)); // Redirect to main app window.location.href = 'web-app.html'; } function goBack() { if (window.opener) { window.close(); } else { window.location.href = 'web-app.html'; } } function refreshPreview() { updatePreview(); } function showLoading() { document.getElementById('loadingIndicator').style.display = 'block'; document.getElementById('createButton').disabled = true; } function hideLoading() { document.getElementById('loadingIndicator').style.display = 'none'; document.getElementById('createButton').disabled = false; } function showNotification(message, type = 'info') { // Create notification element const notification = document.createElement('div'); notification.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 12px 20px; border-radius: 8px; color: white; font-weight: 500; z-index: 3000; opacity: 0; transform: translateX(100%); transition: all 0.3s ease; max-width: 400px; `; let backgroundColor; switch (type) { case 'error': backgroundColor = '#f44336'; break; case 'success': backgroundColor = '#4caf50'; break; case 'warning': backgroundColor = '#ff9800'; break; default: backgroundColor = '#667eea'; } notification.style.backgroundColor = backgroundColor; notification.textContent = message; document.body.appendChild(notification); // Animate in setTimeout(() => { notification.style.opacity = '1'; notification.style.transform = 'translateX(0)'; }, 100); // Animate out and remove setTimeout(() => { notification.style.opacity = '0'; notification.style.transform = 'translateX(100%)'; setTimeout(() => { document.body.removeChild(notification); }, 300); }, 3000); } </script> </body> </html>