Changeset 0.30.2 (#201)

Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit is contained in:
2026-03-18 15:10:11 +00:00
committed by xcaliber
parent 8fee53e440
commit 7b0b6eb061
22 changed files with 1217 additions and 47 deletions

View File

@@ -594,7 +594,7 @@ type WorkflowPageData struct {
ChannelDescription string
SessionID string
SessionName string
StageMode string // chat_only | form_only | form_chat
StageMode string // chat_only | form_only | form_chat | review
StageName string
FormTemplateJSON string // typed form template JSON (empty if chat_only)
TotalStages int

View File

@@ -163,9 +163,8 @@
</div>
{{end}}
<!-- form_only: form only -->
<!-- form_chat: split form + chat -->
<!-- chat_only: chat only (original behavior) -->
<!-- Stage surface mount point (v0.30.2) -->
<!-- Modes: form_only, form_chat, chat_only, review -->
{{if eq .Data.StageMode "form_only"}}
<div class="wf-form" id="formArea"></div>
@@ -181,6 +180,8 @@
</div>
</div>
</div>
{{else if eq .Data.StageMode "review"}}
<div id="reviewArea" style="flex:1;overflow-y:auto"></div>
{{else}}
<div class="wf-chat" id="chatMessages"></div>
<div class="wf-input">
@@ -191,7 +192,7 @@
{{end}}
<div class="wf-session-info">
{{if eq .Data.StageMode "form_only"}}Submitting as{{else}}Chatting as{{end}} <strong>{{.Data.SessionName}}</strong>
{{if eq .Data.StageMode "form_only"}}Submitting as{{else if eq .Data.StageMode "review"}}Reviewing as{{else}}Chatting as{{end}} <strong>{{.Data.SessionName}}</strong>
</div>
</div>
@@ -437,6 +438,93 @@
this.style.height = Math.min(this.scrollHeight, 160) + 'px';
});
}
// ── Review surface (v0.30.2) ──────────
if (STAGE_MODE === 'review') {
var reviewArea = document.getElementById('reviewArea');
if (reviewArea) {
loadReviewSurface(reviewArea);
}
}
async function loadReviewSurface(container) {
container.innerHTML = '<div style="padding:24px;text-align:center;color:var(--text-3)">Loading review data\u2026</div>';
var html = '<div style="padding:24px;max-width:640px;margin:0 auto">';
html += '<h3 style="margin-bottom:16px">Review</h3>';
try {
var resp = await fetch(BASE + '/api/v1/channels/' + CHAN_ID + '/workflow/status', {
headers: { 'Content-Type': 'application/json' },
});
if (resp.ok) {
var status = await resp.json();
html += '<div style="margin-bottom:16px">';
html += '<h4 style="margin-bottom:8px;font-size:14px;color:var(--text-2)">Collected Data</h4>';
if (status.stage_data && typeof status.stage_data === 'object' && Object.keys(status.stage_data).length > 0) {
html += '<table style="width:100%;border-collapse:collapse">';
for (var key in status.stage_data) {
if (!status.stage_data.hasOwnProperty(key)) continue;
html += '<tr style="border-bottom:1px solid var(--border)">';
html += '<td style="padding:8px;font-weight:600;font-size:13px;width:30%">' + escHtml(key) + '</td>';
html += '<td style="padding:8px;font-size:13px">' + escHtml(String(status.stage_data[key])) + '</td>';
html += '</tr>';
}
html += '</table>';
} else {
html += '<p style="color:var(--text-3)">No data collected yet.</p>';
}
html += '</div>';
}
} catch(e) {
html += '<p style="color:var(--danger)">Failed to load review data.</p>';
}
html += '<div style="display:flex;gap:8px;margin-top:24px">';
html += '<button id="reviewAdvanceBtn" style="background:var(--accent);color:#fff;border:none;border-radius:8px;padding:10px 24px;font-weight:600;cursor:pointer;font-size:14px">Approve &amp; Advance</button>';
html += '<button id="reviewRejectBtn" style="background:var(--danger,#e74c3c);color:#fff;border:none;border-radius:8px;padding:10px 24px;font-weight:600;cursor:pointer;font-size:14px">Reject</button>';
html += '</div></div>';
container.innerHTML = html;
document.getElementById('reviewAdvanceBtn').addEventListener('click', async function() {
this.disabled = true;
try {
var r = await fetch(BASE + '/api/v1/channels/' + CHAN_ID + '/workflow/advance', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data: {} }),
});
if (r.ok) {
container.innerHTML = '<div style="padding:40px;text-align:center"><h3>Approved</h3><p style="color:var(--text-2)">Stage advanced.</p></div>';
setTimeout(function() { window.location.reload(); }, 1500);
} else {
var err = await r.json().catch(function() { return {}; });
alert('Failed: ' + (err.error || 'unknown'));
document.getElementById('reviewAdvanceBtn').disabled = false;
}
} catch(e) { alert('Error: ' + e.message); }
});
document.getElementById('reviewRejectBtn').addEventListener('click', async function() {
var reason = prompt('Rejection reason:');
if (!reason) return;
this.disabled = true;
try {
var r = await fetch(BASE + '/api/v1/channels/' + CHAN_ID + '/workflow/reject', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ reason: reason }),
});
if (r.ok) {
container.innerHTML = '<div style="padding:40px;text-align:center"><h3>Rejected</h3><p style="color:var(--text-2)">Sent back for revision.</p></div>';
setTimeout(function() { window.location.reload(); }, 1500);
} else {
var err = await r.json().catch(function() { return {}; });
alert('Failed: ' + (err.error || 'unknown'));
document.getElementById('reviewRejectBtn').disabled = false;
}
} catch(e) { alert('Error: ' + e.message); }
});
}
})();
</script>
</body>