QuestionPrompt has a questions array, not flat fields. Updated rendering to iterate over each question item with its own header, question text, and options.
826 lines
22 KiB
HTML
826 lines
22 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>claude-remote</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
background: #1a1a1a;
|
|
color: #e0e0e0;
|
|
padding: 0;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
header {
|
|
background: #2a2a2a;
|
|
padding: 16px;
|
|
border-bottom: 2px solid #3a3a3a;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
h1 {
|
|
font-size: 20px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.status {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 12px;
|
|
color: #888;
|
|
}
|
|
|
|
.status-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: #888;
|
|
}
|
|
|
|
.status.connected .status-dot {
|
|
background: #4CAF50;
|
|
}
|
|
|
|
.container {
|
|
padding: 16px;
|
|
max-width: 100%;
|
|
}
|
|
|
|
.section {
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: #888;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.session {
|
|
background: #2a2a2a;
|
|
border: 1px solid #3a3a3a;
|
|
border-radius: 8px;
|
|
margin-bottom: 12px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.session-header {
|
|
padding: 12px 16px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
min-height: 44px;
|
|
}
|
|
|
|
.session-info {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.session-command {
|
|
font-weight: 600;
|
|
font-size: 14px;
|
|
margin-bottom: 4px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.session-cwd {
|
|
font-size: 12px;
|
|
color: #888;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.session-toggle {
|
|
font-size: 18px;
|
|
color: #888;
|
|
transition: transform 0.2s;
|
|
}
|
|
|
|
.session.expanded .session-toggle {
|
|
transform: rotate(180deg);
|
|
}
|
|
|
|
.session-output {
|
|
display: none;
|
|
padding: 16px;
|
|
background: #1a1a1a;
|
|
border-top: 1px solid #3a3a3a;
|
|
max-height: 400px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.session.expanded .session-output {
|
|
display: block;
|
|
}
|
|
|
|
.terminal {
|
|
font-family: "SF Mono", Monaco, "Cascadia Code", "Courier New", monospace;
|
|
font-size: 12px;
|
|
line-height: 1.5;
|
|
white-space: pre-wrap;
|
|
word-wrap: break-word;
|
|
color: #e0e0e0;
|
|
}
|
|
|
|
.prompt {
|
|
background: #2a2a2a;
|
|
border: 2px solid #ff9800;
|
|
border-radius: 8px;
|
|
padding: 16px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.prompt-text {
|
|
font-size: 14px;
|
|
margin-bottom: 12px;
|
|
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;
|
|
}
|
|
|
|
.btn {
|
|
flex: 1;
|
|
padding: 12px;
|
|
border: none;
|
|
border-radius: 6px;
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
min-height: 48px;
|
|
transition: opacity 0.2s;
|
|
}
|
|
|
|
.btn:active {
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.btn:disabled {
|
|
opacity: 0.5;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.btn-approve {
|
|
background: #4CAF50;
|
|
color: white;
|
|
}
|
|
|
|
.btn-reject {
|
|
background: #f44336;
|
|
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;
|
|
color: #888;
|
|
font-size: 14px;
|
|
}
|
|
|
|
@media (prefers-color-scheme: light) {
|
|
body {
|
|
background: #f5f5f5;
|
|
color: #1a1a1a;
|
|
}
|
|
|
|
header {
|
|
background: white;
|
|
border-bottom-color: #e0e0e0;
|
|
}
|
|
|
|
.session,
|
|
.prompt {
|
|
background: white;
|
|
border-color: #ff9800;
|
|
}
|
|
|
|
.session-output {
|
|
background: #f5f5f5;
|
|
border-top-color: #e0e0e0;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>claude-remote</h1>
|
|
<div class="status" id="status">
|
|
<span class="status-dot"></span>
|
|
<span class="status-text">connecting</span>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="container">
|
|
<div class="section">
|
|
<div class="section-title">Active Sessions</div>
|
|
<div id="sessions">
|
|
<div class="empty-state">No active sessions</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<div class="section-title">Pending Prompts</div>
|
|
<div id="prompts">
|
|
<div class="empty-state">No pending prompts</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
const state = {
|
|
sessions: new Map(),
|
|
prompts: new Map(),
|
|
eventSource: null,
|
|
reconnectTimeout: null,
|
|
};
|
|
|
|
const $status = document.getElementById('status');
|
|
const $sessions = document.getElementById('sessions');
|
|
const $prompts = document.getElementById('prompts');
|
|
|
|
function setStatus(connected) {
|
|
if (connected) {
|
|
$status.classList.add('connected');
|
|
$status.querySelector('.status-text').textContent = 'connected';
|
|
} else {
|
|
$status.classList.remove('connected');
|
|
$status.querySelector('.status-text').textContent = 'connecting';
|
|
}
|
|
}
|
|
|
|
function connectSSE() {
|
|
if (state.eventSource) {
|
|
state.eventSource.close();
|
|
}
|
|
|
|
const es = new EventSource('/events');
|
|
|
|
es.addEventListener('open', () => {
|
|
console.debug('SSE connected');
|
|
setStatus(true);
|
|
if (state.reconnectTimeout) {
|
|
clearTimeout(state.reconnectTimeout);
|
|
state.reconnectTimeout = null;
|
|
}
|
|
});
|
|
|
|
es.addEventListener('error', () => {
|
|
console.debug('SSE error, reconnecting...');
|
|
setStatus(false);
|
|
es.close();
|
|
if (!state.reconnectTimeout) {
|
|
state.reconnectTimeout = setTimeout(() => connectSSE(), 2000);
|
|
}
|
|
});
|
|
|
|
es.addEventListener('session_start', (e) => {
|
|
const data = JSON.parse(e.data);
|
|
state.sessions.set(data.session_id, {
|
|
id: data.session_id,
|
|
cwd: data.cwd,
|
|
command: data.command,
|
|
output: '',
|
|
outputRenderedLength: 0,
|
|
expanded: false,
|
|
});
|
|
renderSessions();
|
|
});
|
|
|
|
es.addEventListener('session_end', (e) => {
|
|
const data = JSON.parse(e.data);
|
|
state.sessions.delete(data.session_id);
|
|
renderSessions();
|
|
});
|
|
|
|
es.addEventListener('output', (e) => {
|
|
const data = JSON.parse(e.data);
|
|
const session = state.sessions.get(data.session_id);
|
|
if (session) {
|
|
session.output += data.data;
|
|
renderSessionOutput(data.session_id);
|
|
}
|
|
});
|
|
|
|
es.addEventListener('prompt', (e) => {
|
|
const data = JSON.parse(e.data);
|
|
state.prompts.set(data.prompt_id, {
|
|
id: data.prompt_id,
|
|
session_id: data.session_id,
|
|
text: data.prompt_text,
|
|
json: data.prompt_json ? JSON.parse(data.prompt_json) : null,
|
|
});
|
|
renderPrompts();
|
|
});
|
|
|
|
es.addEventListener('prompt_response', (e) => {
|
|
const data = JSON.parse(e.data);
|
|
state.prompts.delete(data.prompt_id);
|
|
renderPrompts();
|
|
});
|
|
|
|
state.eventSource = es;
|
|
}
|
|
|
|
window.toggleSession = (sessionId) => {
|
|
const session = state.sessions.get(Number(sessionId));
|
|
if (session) {
|
|
session.expanded = !session.expanded;
|
|
// Reset rendered length when collapsing or expanding (element gets recreated)
|
|
session.outputRenderedLength = 0;
|
|
renderSessions();
|
|
}
|
|
};
|
|
|
|
function renderSessions() {
|
|
if (state.sessions.size === 0) {
|
|
$sessions.innerHTML = '<div class="empty-state">No active sessions</div>';
|
|
return;
|
|
}
|
|
|
|
$sessions.innerHTML = Array.from(state.sessions.values()).map(s => `
|
|
<div class="session ${s.expanded ? 'expanded' : ''}" data-session="${s.id}">
|
|
<div class="session-header" onclick="toggleSession('${s.id}')">
|
|
<div class="session-info">
|
|
<div class="session-command">${escapeHtml(s.command || 'claude')}</div>
|
|
<div class="session-cwd">${escapeHtml(s.cwd || '~')}</div>
|
|
</div>
|
|
<div class="session-toggle">▼</div>
|
|
</div>
|
|
<div class="session-output">
|
|
<div class="terminal" id="output-${s.id}">${escapeHtml(s.output)}</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
function renderSessionOutput(sessionId) {
|
|
const session = state.sessions.get(sessionId);
|
|
if (!session) return;
|
|
|
|
const $output = document.getElementById(`output-${sessionId}`);
|
|
if ($output) {
|
|
// Append only new content since last render
|
|
const newChunk = session.output.slice(session.outputRenderedLength);
|
|
if (newChunk) {
|
|
$output.textContent += newChunk;
|
|
session.outputRenderedLength = session.output.length;
|
|
}
|
|
if (session.expanded) {
|
|
$output.parentElement.scrollTop = $output.parentElement.scrollHeight;
|
|
}
|
|
}
|
|
}
|
|
|
|
function renderPrompts() {
|
|
if (state.prompts.size === 0) {
|
|
$prompts.innerHTML = '<div class="empty-state">No pending prompts</div>';
|
|
return;
|
|
}
|
|
|
|
$prompts.innerHTML = Array.from(state.prompts.values()).map(p => {
|
|
if (p.json) {
|
|
return renderRichPrompt(p);
|
|
} else {
|
|
// Fallback for old-style prompts
|
|
return `
|
|
<div class="prompt" data-prompt="${p.id}">
|
|
<div class="prompt-text">${escapeHtml(p.text)}</div>
|
|
<div class="prompt-actions">
|
|
<button class="btn btn-approve" onclick="respondToPrompt('${p.id}', 'approve')">Approve</button>
|
|
<button class="btn btn-reject" onclick="respondToPrompt('${p.id}', 'reject')">Reject</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}).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 `
|
|
<div class="prompt" data-prompt="${p.id}">
|
|
<div class="prompt-text">${escapeHtml(p.text)}</div>
|
|
<div class="prompt-actions">
|
|
<button class="btn btn-approve" onclick="respondToPrompt('${p.id}', 'approve')">Approve</button>
|
|
<button class="btn btn-reject" onclick="respondToPrompt('${p.id}', 'reject')">Reject</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
}
|
|
|
|
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) => `
|
|
<label class="prompt-option" onclick="selectOption('${p.id}', ${idx})">
|
|
<input type="radio" name="prompt-${p.id}" value="${escapeHtml(opt.value)}" ${opt.value === selectedOption ? 'checked' : ''}>
|
|
<span class="prompt-option-label">${escapeHtml(opt.label)}</span>
|
|
</label>
|
|
`).join('');
|
|
|
|
return `
|
|
<div class="prompt" data-prompt="${p.id}">
|
|
<div class="prompt-tool">Tool: <span class="prompt-tool-name">${escapeHtml(toolName)}</span></div>
|
|
${toolInputDisplay ? `<div class="prompt-tool-input">${escapeHtml(toolInputDisplay)}</div>` : ''}
|
|
<div class="prompt-question">${escapeHtml(p.text)}</div>
|
|
<div class="prompt-options">
|
|
${optionsHtml}
|
|
</div>
|
|
<div class="prompt-actions">
|
|
<button class="btn btn-submit" onclick="submitRichPrompt('${p.id}')">Submit</button>
|
|
${allowsTabInstructions ? '<button class="btn btn-secondary" disabled data-tooltip="Coming soon">Add instructions</button>' : ''}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function renderQuestionPrompt(p) {
|
|
const json = p.json;
|
|
const questions = json.questions || [];
|
|
|
|
const questionsHtml = questions.map((q, qIdx) => {
|
|
const header = q.header || '';
|
|
const question = q.question || '';
|
|
const options = q.options || [];
|
|
const multiSelect = q.multi_select || false;
|
|
const allowsOther = q.allows_other || false;
|
|
const inputType = multiSelect ? 'checkbox' : 'radio';
|
|
const inputName = `prompt-${p.id}-${qIdx}`;
|
|
|
|
const optionsHtml = options.map((opt, idx) => `
|
|
<label class="prompt-option" onclick="selectOption('${p.id}', ${idx})">
|
|
<input type="${inputType}" name="${inputName}" value="${escapeHtml(opt.value)}">
|
|
<span class="prompt-option-label">${escapeHtml(opt.label)}</span>
|
|
</label>
|
|
`).join('');
|
|
|
|
const otherHtml = allowsOther ? `
|
|
<label class="prompt-option">
|
|
<input type="${inputType}" name="${inputName}" value="__other__" onclick="enableOtherInput('${p.id}-${qIdx}')">
|
|
<span class="prompt-option-label">Other</span>
|
|
</label>
|
|
<input type="text" class="prompt-other-input" id="other-${p.id}-${qIdx}" placeholder="Enter your answer..." disabled>
|
|
` : '';
|
|
|
|
return `
|
|
${header ? `<div class="prompt-header">${escapeHtml(header)}</div>` : ''}
|
|
<div class="prompt-question">${escapeHtml(question)}</div>
|
|
<div class="prompt-options">
|
|
${optionsHtml}
|
|
${otherHtml}
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
return `
|
|
<div class="prompt" data-prompt="${p.id}">
|
|
${questionsHtml}
|
|
<div class="prompt-actions">
|
|
<button class="btn btn-submit" onclick="submitRichPrompt('${p.id}')">Submit</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function renderExitPlanPrompt(p) {
|
|
const json = p.json;
|
|
const options = json.options || [];
|
|
|
|
const optionsHtml = options.map((opt, idx) => `
|
|
<label class="prompt-option" onclick="selectOption('${p.id}', ${idx})">
|
|
<input type="radio" name="prompt-${p.id}" value="${escapeHtml(opt)}">
|
|
<span class="prompt-option-label">${escapeHtml(opt)}</span>
|
|
</label>
|
|
`).join('');
|
|
|
|
return `
|
|
<div class="prompt" data-prompt="${p.id}">
|
|
<div class="prompt-header">Plan Complete</div>
|
|
<div class="prompt-question">${escapeHtml(p.text)}</div>
|
|
<div class="prompt-options">
|
|
${optionsHtml}
|
|
</div>
|
|
<div class="prompt-actions">
|
|
<button class="btn btn-submit" onclick="submitRichPrompt('${p.id}')">Submit</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
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 {
|
|
const res = await fetch(`/api/prompts/${promptId}/${action}`, {
|
|
method: 'POST',
|
|
});
|
|
if (!res.ok) {
|
|
console.error(`Failed to ${action} prompt:`, await res.text());
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error responding to prompt:`, error);
|
|
}
|
|
};
|
|
|
|
function escapeHtml(text) {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
}
|
|
|
|
// Initialize
|
|
connectSSE();
|
|
</script>
|
|
</body>
|
|
</html>
|