Skip to main content
Glama
index.html24.1 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Voice Mode for Claude Code - Messenger</title> <style> * { box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 0; background-color: #f5f5f5; height: 100vh; overflow: hidden; } .app-container { display: flex; flex-direction: column; height: 100vh; max-width: 1200px; margin: 0 auto; background: white; } /* Header - Neo-Brutalist Style */ .app-header { display: flex; justify-content: space-between; align-items: center; padding: 20px; background: #E0E0E0; color: #000; border-bottom: 6px solid #666; box-shadow: 8px 8px 0 #666; } .app-header h1 { margin: 0; font-family: 'Courier New', 'Courier', monospace; font-size: 24px; font-weight: 900; text-transform: uppercase; letter-spacing: -1px; } .legacy-link { color: #666; text-decoration: none; font-family: 'Courier New', 'Courier', monospace; font-size: 12px; font-weight: 400; white-space: nowrap; } .legacy-link:hover { text-decoration: underline; } /* Main Content Area */ .main-content { flex: 1; display: flex; flex-direction: column; overflow: hidden; } /* Conversation Section */ .conversation-section { flex: 1; display: flex; flex-direction: column; overflow: hidden; } .conversation-header { display: flex; justify-content: space-between; align-items: center; padding: 12px 20px; border-bottom: 1px solid #E0E0E0; background: #FAFAFA; } .conversation-title { font-size: 16px; font-weight: 600; color: #333; } .conversation-actions { display: flex; gap: 8px; } .icon-button { background: white; border: 1px solid #DDD; border-radius: 6px; padding: 8px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s; } .icon-button:hover { background: #F0F0F0; border-color: #BBB; } .icon-button.danger:hover { background: #FFEBEE; border-color: #EF5350; } .icon-button svg { fill: #666; } .icon-button.danger:hover svg { fill: #EF5350; } /* Conversation Container */ .conversation-container { flex: 1; overflow-y: auto; background: #F5F5F5; position: relative; } .conversation-messages { display: flex; flex-direction: column; padding: 20px; gap: 12px; min-height: 100%; } /* Empty State */ .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #999; text-align: center; padding: 40px 20px; } .empty-state svg { margin-bottom: 16px; } .empty-state p { margin: 0; font-size: 16px; } /* Waiting Indicator */ .waiting-indicator { text-align: center; color: #999; font-size: 14px; font-style: italic; padding: 12px 20px; margin-top: 8px; } /* Message Bubbles */ .message-bubble { max-width: 70%; padding: 10px 14px; border-radius: 18px; word-wrap: break-word; position: relative; animation: messageSlideIn 0.2s ease-out; } @keyframes messageSlideIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } /* User Messages (Right Side, Blue) */ .message-bubble.user { align-self: flex-end; background: #007AFF; color: white; border-bottom-right-radius: 4px; } /* Assistant Messages (Left Side, Gray) */ .message-bubble.assistant { align-self: flex-start; background: #E5E5EA; color: #000; border-bottom-left-radius: 4px; } .message-text { font-size: 15px; line-height: 1.4; white-space: pre-wrap; } .message-meta { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin-top: 6px; font-size: 11px; opacity: 0.7; } .message-bubble.user .message-meta { color: rgba(255, 255, 255, 0.8); } .message-bubble.assistant .message-meta { color: rgba(0, 0, 0, 0.5); } .message-timestamp { white-space: nowrap; } .message-status { font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; white-space: nowrap; display: flex; align-items: center; gap: 6px; } .delete-message-btn { cursor: pointer; opacity: 0.5; transition: opacity 0.2s; } .delete-message-btn:hover { opacity: 1; } .delete-icon { width: 14px; height: 14px; fill: currentColor; } /* Status Colors for User Messages */ .message-bubble.user .message-status.pending { color: #FFE082; } .message-bubble.user .message-status.delivered { color: #B3E5FC; } .message-bubble.user .message-status.responded { color: #C8E6C9; } /* Scrollbar Styling */ .conversation-container::-webkit-scrollbar { width: 8px; } .conversation-container::-webkit-scrollbar-track { background: #F5F5F5; } .conversation-container::-webkit-scrollbar-thumb { background: #CCC; border-radius: 4px; } .conversation-container::-webkit-scrollbar-thumb:hover { background: #AAA; } /* Input Section */ .input-section { border-top: 1px solid #E0E0E0; background: white; padding: 16px 20px; } /* Send Mode Controls */ .send-mode-controls { display: flex; align-items: center; gap: 16px; margin-bottom: 12px; padding: 8px; background: #F8F9FA; border-radius: 8px; } .send-mode-radio { display: flex; align-items: center; gap: 6px; } .send-mode-radio input[type="radio"] { cursor: pointer; } .send-mode-radio label { font-size: 14px; color: #666; cursor: pointer; } .trigger-word-input { display: flex; align-items: center; gap: 8px; } .trigger-word-input label { font-size: 13px; color: #666; } .trigger-word-input input { padding: 4px 8px; border: 1px solid #DDD; border-radius: 4px; font-size: 13px; width: 120px; } /* Text Input Container */ .input-container { display: flex; align-items: flex-end; gap: 8px; background: #F8F9FA; border: 2px solid #E0E0E0; border-radius: 24px; padding: 8px 8px 8px 16px; transition: border-color 0.2s; } .input-container:focus-within { border-color: #007AFF; } /* Text Input */ .message-input { flex: 1; border: none; background: transparent; resize: none; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 15px; line-height: 1.4; padding: 6px 0; max-height: 120px; overflow-y: auto; outline: none; } .message-input::placeholder { color: #999; } /* Microphone Button */ .mic-button { width: 40px; height: 40px; border-radius: 50%; border: none; background: #007AFF; color: white; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s; flex-shrink: 0; } .mic-button:hover { background: #0051D5; transform: scale(1.05); } .mic-button:active { transform: scale(0.95); } .mic-button.listening { background: #EF5350; animation: micPulse 1.5s ease-in-out infinite; } @keyframes micPulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(239, 83, 80, 0.7); } 50% { box-shadow: 0 0 0 8px rgba(239, 83, 80, 0); } } .mic-icon { width: 20px; height: 20px; fill: currentColor; } /* Listening Indicator */ /* Settings Toggle */ .settings-toggle { margin-top: 16px; padding-top: 16px; border-top: 1px solid #E0E0E0; } .toggle-header { display: flex; justify-content: space-between; align-items: center; cursor: pointer; padding: 8px; border-radius: 6px; transition: background 0.2s; } .toggle-header:hover { background: #F8F9FA; } .toggle-header h3 { margin: 0; font-size: 15px; color: #666; } .toggle-arrow { transition: transform 0.2s; } .toggle-arrow.open { transform: rotate(180deg); } .settings-content { display: none; padding: 12px 8px; } .settings-content.open { display: block; } /* Voice Response Toggle */ .voice-response-control { display: flex; align-items: center; justify-content: space-between; padding: 12px; background: #F8F9FA; border-radius: 8px; margin-bottom: 12px; } .voice-response-label { font-size: 15px; color: #333; font-weight: 500; } .toggle-switch { position: relative; width: 51px; height: 31px; } .toggle-switch input { opacity: 0; width: 0; height: 0; } .toggle-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .4s; border-radius: 31px; } .toggle-slider:before { position: absolute; content: ""; height: 23px; width: 23px; left: 4px; bottom: 4px; background-color: white; transition: .4s; border-radius: 50%; } .toggle-switch input:checked + .toggle-slider { background-color: #007AFF; } .toggle-switch input:checked + .toggle-slider:before { transform: translateX(20px); } /* TTS Controls */ .tts-controls { display: flex; flex-direction: column; gap: 12px; } .tts-control { display: flex; flex-direction: column; gap: 4px; } .tts-control label { font-size: 13px; color: #666; font-weight: 500; } .tts-control select, .tts-control input[type="range"], .tts-control input[type="number"] { padding: 6px 8px; border: 1px solid #DDD; border-radius: 6px; font-size: 14px; } .tts-control input[type="number"] { width: 80px; margin-top: 4px; } .system-voice-info, .rate-warning { padding: 8px 12px; border-radius: 6px; font-size: 12px; display: flex; align-items: center; gap: 8px; } .system-voice-info { background: #E3F2FD; border: 1px solid #2196F3; color: #1565C0; } .rate-warning { background: #FFF3E0; border: 1px solid #FF9800; color: #E65100; } .info-text a, .warning-text { color: inherit; } .tts-test-btn { padding: 10px 16px; background: #007AFF; color: white; border: none; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; transition: background 0.2s; } .tts-test-btn:hover { background: #0051D5; } /* Mobile Responsive Styles */ @media (max-width: 768px) { .app-container { max-width: 100%; } .app-header { padding: 12px 16px; } .app-header h1 { font-size: 18px; } .app-header .subtitle { font-size: 12px; } .conversation-header { padding: 10px 16px; } .conversation-title { font-size: 14px; } .conversation-messages { padding: 12px; gap: 8px; } .message-bubble { max-width: 85%; font-size: 14px; padding: 8px 12px; } .message-text { font-size: 14px; } .message-meta { font-size: 10px; } .input-section { padding: 12px 16px; } .send-mode-controls { flex-direction: column; align-items: flex-start; gap: 8px; } .trigger-word-input { width: 100%; } .trigger-word-input input { flex: 1; } .input-container { padding: 6px 6px 6px 12px; } .message-input { font-size: 16px; /* Prevents zoom on iOS */ } .mic-button { width: 36px; height: 36px; } .mic-icon { width: 18px; height: 18px; } .icon-button { padding: 6px; } .icon-button svg { width: 18px; height: 18px; } } /* Very small mobile screens */ @media (max-width: 480px) { body { padding: 0; } .app-header { padding: 10px 12px; } .conversation-messages { padding: 8px; } .message-bubble { max-width: 90%; padding: 6px 10px; } .input-section { padding: 10px 12px; } .send-mode-controls { font-size: 13px; } } </style> </head> <body> <div class="app-container"> <!-- Header --> <div class="app-header"> <h1>Voice Mode for Claude Code</h1> <a href="/legacy" class="legacy-link">Switch to Legacy UI</a> </div> <!-- Main Content --> <div class="main-content"> <!-- Conversation Section --> <div class="conversation-section"> <!-- Conversation Messages --> <div id="conversationContainer" class="conversation-container"> <div id="conversationMessages" class="conversation-messages"> <div class="empty-state"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="48" height="48" fill="#999"> <path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/> </svg> <p id="emptyStateMessage">No messages yet. Type or speak to start the conversation!</p> </div> <div id="waitingIndicator" class="waiting-indicator" style="display: none;"> Claude is waiting... </div> </div> </div> </div> <!-- Input Section --> <div class="input-section"> <!-- Send Mode Controls --> <div class="send-mode-controls"> <div class="send-mode-radio"> <input type="radio" id="autoMode" name="sendMode" value="automatic" checked> <label for="autoMode">Auto-send on pause</label> </div> <div class="send-mode-radio"> <input type="radio" id="triggerMode" name="sendMode" value="trigger"> <label for="triggerMode">Wait for trigger word</label> </div> <div class="trigger-word-input" id="triggerWordInputContainer" style="display: none;"> <label for="triggerWordInput">Trigger:</label> <input type="text" id="triggerWordInput" placeholder="e.g., send, go" value="send" > </div> </div> <!-- Text Input with Microphone --> <div class="input-container"> <textarea id="messageInput" class="message-input" placeholder="Type a message or use voice..." rows="1" ></textarea> <button id="micBtn" class="mic-button" title="Voice dictation"> <svg class="mic-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <path d="M12 1C10.34 1 9 2.34 9 4V12C9 13.66 10.34 15 12 15C13.66 15 15 13.66 15 12V4C15 2.34 13.66 1 12 1ZM19 12C19 15.53 16.39 18.44 13 18.93V22H11V18.93C7.61 18.44 5 15.53 5 12H7C7 14.76 9.24 17 12 17C14.76 17 17 14.76 17 12H19Z" /> </svg> </button> </div> <!-- Settings Toggle --> <div class="settings-toggle"> <div class="toggle-header" id="settingsToggleHeader"> <h3>Voice Settings</h3> <span class="toggle-arrow">▼</span> </div> <div class="settings-content" id="settingsContent"> <div class="voice-response-control"> <span class="voice-response-label">Voice Responses</span> <label class="toggle-switch"> <input type="checkbox" id="voiceResponsesToggle"> <span class="toggle-slider"></span> </label> </div> <div class="tts-controls" id="voiceOptions" style="display: none; margin-top: 12px;"> <div class="tts-control"> <label for="languageSelect">Language:</label> <select id="languageSelect"> <option value="en-US">Loading languages...</option> </select> </div> <div class="tts-control"> <label for="voiceSelect">Voice:</label> <select id="voiceSelect"> <option value="system">Mac System Voice</option> <optgroup label="Browser Voices (Cloud)" id="cloudVoicesGroup"> </optgroup> <optgroup label="Browser Voices (Local)" id="localVoicesGroup"> </optgroup> </select> </div> <div id="systemVoiceInfo" class="system-voice-info" style="display: none;"> <span class="info-icon">ℹ️</span> <span class="info-text">Download high quality voices in Mac System Settings. <a href="https://github.com/johnmatthewtennant/mcp-voice-hooks?tab=readme-ov-file#voice-responses" target="_blank">Learn more</a></span> </div> <div id="rateWarning" class="rate-warning" style="display: none;"> <span class="warning-icon">⚠️</span> <span class="warning-text">Google voices may not respond well to rate adjustments</span> </div> <div class="tts-control"> <label for="speechRate">Speaking Rate:</label> <input type="range" id="speechRate" min="0.5" max="5" step="0.1" value="1"> <input type="number" id="speechRateInput" min="0.5" max="5" step="0.1" value="1"> </div> <button class="tts-test-btn" id="testTTSBtn">Test Voice</button> </div> </div> </div> </div> </div> </div> <script src="app.js"></script> </body> </html>

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/johnmatthewtennant/mcp-voice-hooks'

If you have feedback or need assistance with the MCP directory API, please join our Discord server