From d3fac77f6f3039292d9f7a4c1777fd9791d18966 Mon Sep 17 00:00:00 2001 From: Jared Miller Date: Wed, 28 Jan 2026 12:59:23 -0500 Subject: [PATCH] Update dashboard to render rich multi-option prompts --- public/index.html | 398 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 390 insertions(+), 8 deletions(-) diff --git a/public/index.html b/public/index.html index 730fb78..cf8663b 100644 --- a/public/index.html +++ b/public/index.html @@ -144,7 +144,7 @@ .prompt { background: #2a2a2a; - border: 1px solid #ff9800; + border: 2px solid #ff9800; border-radius: 8px; padding: 16px; margin-bottom: 12px; @@ -156,6 +156,93 @@ line-height: 1.5; } + .prompt-header { + font-size: 16px; + font-weight: 600; + margin-bottom: 12px; + color: #ff9800; + } + + .prompt-tool { + font-size: 14px; + font-weight: 600; + margin-bottom: 8px; + color: #888; + } + + .prompt-tool-name { + color: #e0e0e0; + } + + .prompt-tool-input { + font-size: 13px; + font-family: "SF Mono", Monaco, "Cascadia Code", "Courier New", monospace; + color: #888; + margin-bottom: 12px; + word-break: break-all; + } + + .prompt-question { + font-size: 14px; + margin-bottom: 16px; + line-height: 1.5; + } + + .prompt-options { + margin-bottom: 16px; + } + + .prompt-option { + display: flex; + align-items: center; + gap: 12px; + padding: 12px; + border: 1px solid #3a3a3a; + border-radius: 6px; + margin-bottom: 8px; + min-height: 48px; + cursor: pointer; + user-select: none; + transition: background 0.2s; + } + + .prompt-option:hover { + background: rgba(255, 255, 255, 0.05); + } + + .prompt-option input[type="radio"], + .prompt-option input[type="checkbox"] { + width: 20px; + height: 20px; + margin: 0; + cursor: pointer; + flex-shrink: 0; + } + + .prompt-option-label { + flex: 1; + font-size: 14px; + line-height: 1.5; + cursor: pointer; + } + + .prompt-other-input { + width: 100%; + padding: 12px; + margin-top: 8px; + border: 1px solid #3a3a3a; + border-radius: 6px; + background: #1a1a1a; + color: #e0e0e0; + font-size: 14px; + font-family: inherit; + } + + .prompt-other-input:focus { + outline: none; + border-color: #ff9800; + } + .prompt-actions { display: flex; gap: 8px; @@ -169,7 +256,7 @@ font-size: 16px; font-weight: 600; cursor: pointer; - min-height: 44px; + min-height: 48px; transition: opacity 0.2s; } @@ -177,6 +264,11 @@ opacity: 0.7; } + .btn:disabled { + opacity: 0.5; + cursor: not-allowed; + } + .btn-approve { background: #4CAF50; color: white; @@ -187,6 +279,34 @@ color: white; } + .btn-submit { + background: #ff9800; + color: white; + } + + .btn-secondary { + background: #3a3a3a; + color: #e0e0e0; + position: relative; + } + + .btn-secondary[data-tooltip]:hover::after { + content: attr(data-tooltip); + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + margin-bottom: 8px; + padding: 8px 12px; + background: #1a1a1a; + color: #e0e0e0; + font-size: 12px; + white-space: nowrap; + border-radius: 4px; + border: 1px solid #3a3a3a; + z-index: 1000; + } + .empty-state { padding: 32px 16px; text-align: center; @@ -208,7 +328,7 @@ .session, .prompt { background: white; - border-color: #e0e0e0; + border-color: #ff9800; } .session-output { @@ -219,6 +339,24 @@ .terminal { color: #1a1a1a; } + + .prompt-option { + border-color: #e0e0e0; + } + + .prompt-option:hover { + background: rgba(0, 0, 0, 0.05); + } + + .prompt-other-input { + background: white; + border-color: #e0e0e0; + color: #1a1a1a; + } + + .prompt-tool-input { + color: #666; + } } @@ -328,6 +466,7 @@ id: data.prompt_id, session_id: data.session_id, text: data.prompt_text, + json: data.prompt_json ? JSON.parse(data.prompt_json) : null, }); renderPrompts(); }); @@ -397,17 +536,260 @@ return; } - $prompts.innerHTML = Array.from(state.prompts.values()).map(p => ` + $prompts.innerHTML = Array.from(state.prompts.values()).map(p => { + if (p.json) { + return renderRichPrompt(p); + } else { + // Fallback for old-style prompts + return ` +
+
${escapeHtml(p.text)}
+
+ + +
+
+ `; + } + }).join(''); + } + + function renderRichPrompt(p) { + const json = p.json; + const promptType = json.prompt_type; + + if (promptType === 'permission') { + return renderPermissionPrompt(p); + } else if (promptType === 'question') { + return renderQuestionPrompt(p); + } else if (promptType === 'exit_plan') { + return renderExitPlanPrompt(p); + } else { + // Unknown type, fallback + return ` +
+
${escapeHtml(p.text)}
+
+ + +
+
+ `; + } + } + + function renderPermissionPrompt(p) { + const json = p.json; + const toolName = json.tool_name || 'Unknown'; + const toolInput = json.tool_input || {}; + const options = json.options || []; + const selectedOption = json.selected_option; + const allowsTabInstructions = json.allows_tab_instructions || false; + + // Extract relevant tool input info + let toolInputDisplay = ''; + if (toolName === 'Write' || toolName === 'Edit' || toolName === 'Read') { + toolInputDisplay = toolInput.file_path || ''; + } else if (toolName === 'Bash') { + toolInputDisplay = toolInput.command || ''; + } else if (toolInput.path) { + toolInputDisplay = toolInput.path; + } + + const optionsHtml = options.map((opt, idx) => ` + + `).join(''); + + return `
-
${escapeHtml(p.text)}
+
Tool: ${escapeHtml(toolName)}
+ ${toolInputDisplay ? `
${escapeHtml(toolInputDisplay)}
` : ''} +
${escapeHtml(p.text)}
+
+ ${optionsHtml} +
- - + + ${allowsTabInstructions ? '' : ''}
- `).join(''); + `; } + function renderQuestionPrompt(p) { + const json = p.json; + const header = json.header || ''; + const question = json.question || p.text; + const options = json.options || []; + const multiSelect = json.multi_select || false; + const allowsOther = json.allows_other || false; + const inputType = multiSelect ? 'checkbox' : 'radio'; + + const optionsHtml = options.map((opt, idx) => ` + + `).join(''); + + const otherHtml = allowsOther ? ` + + + ` : ''; + + return ` +
+ ${header ? `
${escapeHtml(header)}
` : ''} +
${escapeHtml(question)}
+
+ ${optionsHtml} + ${otherHtml} +
+
+ +
+
+ `; + } + + function renderExitPlanPrompt(p) { + const json = p.json; + const options = json.options || []; + + const optionsHtml = options.map((opt, idx) => ` + + `).join(''); + + return ` +
+
Plan Complete
+
${escapeHtml(p.text)}
+
+ ${optionsHtml} +
+
+ +
+
+ `; + } + + window.selectOption = (_promptId, _optionIdx) => { + // Radio buttons and checkboxes handle their own state + // This function is just for accessibility/mobile tap handling + }; + + window.enableOtherInput = (promptId) => { + const input = document.getElementById(`other-${promptId}`); + if (input) { + input.disabled = false; + input.focus(); + } + }; + + window.submitRichPrompt = async (promptId) => { + promptId = Number(promptId); + const prompt = state.prompts.get(promptId); + if (!prompt || !prompt.json) { + console.error('Invalid prompt'); + return; + } + + const json = prompt.json; + let response; + + if (json.prompt_type === 'permission') { + // Get selected radio button + const selected = document.querySelector(`input[name="prompt-${promptId}"]:checked`); + if (!selected) { + console.error('No option selected'); + return; + } + response = { + type: 'option', + value: selected.value, + }; + } else if (json.prompt_type === 'question') { + if (json.multi_select) { + // Get all checked checkboxes + const checked = Array.from(document.querySelectorAll(`input[name="prompt-${promptId}"]:checked`)); + if (checked.length === 0) { + console.error('No options selected'); + return; + } + const values = checked.map(el => { + if (el.value === '__other__') { + const otherInput = document.getElementById(`other-${promptId}`); + return otherInput ? otherInput.value : ''; + } + return el.value; + }); + response = { + type: 'text', + value: values.join(', '), + }; + } else { + // Get selected radio button + const selected = document.querySelector(`input[name="prompt-${promptId}"]:checked`); + if (!selected) { + console.error('No option selected'); + return; + } + if (selected.value === '__other__') { + const otherInput = document.getElementById(`other-${promptId}`); + if (!otherInput || !otherInput.value) { + console.error('Please enter a value for "Other"'); + return; + } + response = { + type: 'text', + value: otherInput.value, + }; + } else { + response = { + type: 'text', + value: selected.value, + }; + } + } + } else if (json.prompt_type === 'exit_plan') { + // Get selected radio button + const selected = document.querySelector(`input[name="prompt-${promptId}"]:checked`); + if (!selected) { + console.error('No option selected'); + return; + } + response = { + type: 'text', + value: selected.value, + }; + } + + try { + const res = await fetch(`/api/prompts/${promptId}/answer`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(response), + }); + if (!res.ok) { + console.error('Failed to submit prompt response:', await res.text()); + } + } catch (error) { + console.error('Error submitting prompt response:', error); + } + }; + window.respondToPrompt = async (promptId, action) => { promptId = Number(promptId); try {