Lumi/plugins/lumi_ai/views/improvement-center.ejs
2026-06-16 08:30:40 +02:00

228 lines
18 KiB
Plaintext

<%- include("../../../src/web/views/partials/layout-top", { title }) %>
<link rel="stylesheet" href="/plugins/lumi_ai/assets/settings.css?v=0.6.0" />
<link rel="stylesheet" href="/plugins/lumi_ai/assets/improvement-center.css?v=0.6.0" />
<section class="ai-titlebar improvement-titlebar">
<div>
<h1>Lumi AI Improvement Center</h1>
<p>Review assistant feedback, stage approved corrections, run evals, and create manual training exports.</p>
</div>
<span class="ai-tag"><%= access.role %><%= access.trusted ? " · trusted reviewer" : "" %></span>
<% if (access.can_approve) { %><a class="button subtle" href="/plugins/lumi_ai">Lumi AI settings</a><% } %>
</section>
<nav class="ai-tabs" aria-label="Improvement Center sections">
<a href="#reviews">Review queue</a>
<a href="#corrections">Corrections</a>
<a href="#evals">Evals</a>
<% if (access.can_export) { %><a href="#exports">Exports</a><% } %>
</nav>
<% if (access.can_approve) { %>
<section class="ai-band">
<div class="ai-section-heading"><div><h2>Access and activation</h2><p>Corrections remain inactive until an administrator selects Save Corrections.</p></div></div>
<form method="post" action="/plugins/lumi_ai/improvement_center/settings" class="form-grid ai-form">
<div class="field"><label><input type="checkbox" name="allow_moderators_to_review_responses" <%= config.improvement.allow_moderators_to_review_responses ? "checked" : "" %> /> Allow moderators to review responses</label></div>
<div class="field"><label><input type="checkbox" name="corrections_enabled" <%= config.improvement.corrections_enabled ? "checked" : "" %> /> Use active approved corrections</label></div>
<div class="field full"><label>Trusted moderator reviewer IDs</label><textarea name="trusted_moderator_reviewers" rows="2"><%= config.improvement.trusted_moderator_reviewers.join("\n") %></textarea></div>
<div class="field full"><button class="button" type="submit">Save access settings</button></div>
</form>
</section>
<% } %>
<section class="ai-band" id="reviews">
<div class="ai-section-heading">
<div><h2>Review queue</h2><p>Feedback records contain the user message, assistant answer, delivery metadata, tag, and optional correction only.</p></div>
<div class="improvement-filters">
<% ["", "pending", "flagged", "verified", "approved", "rejected"].forEach((status) => { %>
<a class="button subtle" href="?status=<%= status %>#reviews"><%= status || "All" %></a>
<% }) %>
</div>
</div>
<div class="improvement-list">
<% reviews.entries.forEach((review) => { %>
<article class="improvement-card">
<header>
<div><strong><%= review.feedback_tag %></strong> <span class="ai-tag"><%= review.feedback_kind || "strict_correction" %></span> <span class="ai-tag"><%= review.status %></span></div>
<span><%= formatDate(review.timestamp) %> · <%= review.role %> · <%= review.platform %> · <%= review.route_used || "unknown route" %></span>
</header>
<div class="improvement-pair">
<div><span>User message</span><pre><%= review.user_message %></pre></div>
<div><span>Assistant answer</span><pre><%= review.assistant_answer %></pre></div>
</div>
<% if (review.optional_correction) { %><div class="improvement-correction"><strong><%= review.feedback_kind === "instruction_based" ? "Instruction guidance" : "Suggested correction" %></strong><pre><%= review.optional_correction %></pre></div><% } %>
<% if (review.review_notes) { %><p class="hint"><strong>Review notes:</strong> <%= review.review_notes %></p><% } %>
<div class="improvement-actions">
<% if (access.can_flag) { %>
<form method="post" action="/plugins/lumi_ai/improvement_center/reviews/<%= review.id %>">
<input type="hidden" name="action" value="flag" />
<button class="button subtle" type="submit">Flag</button>
</form>
<% } %>
<% if (access.can_verify && !["approved", "rejected"].includes(review.status)) { %>
<form method="post" action="/plugins/lumi_ai/improvement_center/reviews/<%= review.id %>">
<input type="hidden" name="action" value="verify" />
<button class="button subtle" type="submit">Verify</button>
</form>
<% } %>
<% if (access.can_approve) { %>
<form method="post" action="/plugins/lumi_ai/improvement_center/reviews/<%= review.id %>">
<input type="hidden" name="action" value="approve" />
<button class="button" type="submit">Approve</button>
</form>
<form method="post" action="/plugins/lumi_ai/improvement_center/reviews/<%= review.id %>">
<input type="hidden" name="action" value="reject" />
<button class="button subtle" type="submit">Reject</button>
</form>
<button class="button subtle" type="button" data-open-dialog="edit-<%= review.id %>">Edit</button>
<% if (review.status === "approved") { %><button class="button" type="button" data-open-dialog="implement-<%= review.id %>">Implement</button><% } %>
<form method="post" action="/plugins/lumi_ai/improvement_center/reviews/<%= review.id %>">
<input type="hidden" name="action" value="export" />
<button class="button subtle" type="submit"><%= review.export_approved ? "Export approved" : "Approve for export" %></button>
</form>
<form method="post" action="/plugins/lumi_ai/improvement_center/reviews/<%= review.id %>" data-improvement-confirm="Delete this feedback record?">
<input type="hidden" name="action" value="delete" />
<button class="button danger" type="submit">Delete</button>
</form>
<% } %>
</div>
</article>
<% if (access.can_edit) { %>
<dialog class="improvement-dialog" id="edit-<%= review.id %>">
<form method="post" action="/plugins/lumi_ai/improvement_center/reviews/<%= review.id %>" class="form-grid ai-form">
<input type="hidden" name="action" value="edit" />
<div class="field"><label>Feedback tag</label><select name="feedback_tag"><% feedbackTags.forEach((tag) => { %><option value="<%= tag %>" <%= tag === review.feedback_tag ? "selected" : "" %>><%= tag %></option><% }) %></select></div>
<div class="field"><label>Feedback type</label><select name="feedback_kind"><% feedbackKinds.forEach((kind) => { %><option value="<%= kind %>" <%= kind === (review.feedback_kind || "strict_correction") ? "selected" : "" %>><%= kind.replaceAll("_", " ") %></option><% }) %></select></div>
<div class="field full"><label>Correction or instruction</label><textarea name="optional_correction" rows="7"><%= review.optional_correction %></textarea></div>
<div class="field full"><label>Review notes</label><textarea name="review_notes" rows="3"><%= review.review_notes %></textarea></div>
<div class="field full improvement-actions"><button class="button" type="submit">Save review</button><button class="button subtle" type="button" data-close-dialog>Cancel</button></div>
</form>
</dialog>
<% } %>
<% if (access.can_implement && review.status === "approved") { %>
<dialog class="improvement-dialog" id="implement-<%= review.id %>">
<form method="post" action="/plugins/lumi_ai/improvement_center/reviews/<%= review.id %>/implement" class="form-grid ai-form">
<div class="field"><label>Promotion target</label><select name="target"><% promotionTargets.forEach((target) => { %><option value="<%= target %>"><%= target.replaceAll("_", " ") %></option><% }) %></select></div>
<div class="field"><label>Minimum role</label><select name="min_role"><% ["user", "mod", "admin"].forEach((role) => { %><option value="<%= role %>" <%= role === review.role ? "selected" : "" %>><%= role %></option><% }) %></select></div>
<div class="field full"><label><%= review.feedback_kind === "instruction_based" ? "Instruction to apply" : "Corrected / expected answer" %></label><textarea name="corrected_answer" rows="7" required><%= review.optional_correction %></textarea></div>
<div class="field"><label>Origin scope</label><input name="permission_origin" value="<%= review.origin || "any" %>" /></div>
<div class="field"><label>Platform scope</label><input name="permission_platform" value="<%= review.platform || "any" %>" /></div>
<div class="field"><label>Route alias</label><input name="route_alias" /></div>
<div class="field"><label>Verified expected link</label><input name="expected_link" placeholder="/verified/path" /></div>
<div class="field full"><label>Forbidden eval behavior</label><textarea name="forbidden_behavior" rows="2"></textarea></div>
<div class="field full"><label>Notes</label><textarea name="notes" rows="2"></textarea></div>
<div class="field"><label><input type="checkbox" name="enabled" checked /> Enabled in staged bank</label></div>
<div class="field"><label><input type="checkbox" name="explicitly_safe" /> Safe for predefined answer</label></div>
<div class="field full improvement-actions"><button class="button" type="submit">Promote review</button><button class="button subtle" type="button" data-close-dialog>Cancel</button></div>
</form>
</dialog>
<% } %>
<% }) %>
<% if (!reviews.entries.length) { %><div class="callout">No feedback matches this filter.</div><% } %>
</div>
<div class="table-pagination">
<a class="button subtle <%= reviews.page <= 1 ? "disabled" : "" %>" href="?review_page=<%= Math.max(1, reviews.page - 1) %>#reviews">Previous</a>
<span class="table-page-label">Page <%= reviews.page %> of <%= reviews.pages %> (<%= reviews.total %> reviews)</span>
<a class="button subtle <%= reviews.page >= reviews.pages ? "disabled" : "" %>" href="?review_page=<%= Math.min(reviews.pages, reviews.page + 1) %>#reviews">Next</a>
</div>
</section>
<section class="ai-band" id="corrections">
<div class="ai-section-heading">
<div><h2>Correction bank</h2><p>Edits and toggles are staged. Save Corrections is required before they become active.</p></div>
<% if (access.can_implement) { %><form method="post" action="/plugins/lumi_ai/improvement_center/corrections/save"><button class="button" type="submit">Save Corrections</button></form><% } %>
</div>
<div class="table-wrap">
<table class="table">
<thead><tr><th>Target</th><th>Prompt / answer</th><th>Permission</th><th>State</th><th>Actions</th></tr></thead>
<tbody>
<% corrections.entries.forEach((entry) => { %>
<tr>
<td><%= entry.target.replaceAll("_", " ") %></td>
<td><details><summary><%= entry.prompt.slice(0, 100) %></summary><pre><%= entry.corrected_answer %></pre></details></td>
<td><%= entry.min_role %> · <%= entry.permission_scope.origin %>/<%= entry.permission_scope.platform %></td>
<td><span class="ai-tag"><%= entry.active ? "active" : entry.enabled ? "staged" : "disabled" %></span></td>
<td class="improvement-actions">
<% if (access.can_verify) { %><form method="post" action="/plugins/lumi_ai/improvement_center/corrections/<%= entry.id %>"><input type="hidden" name="action" value="verify" /><button class="button subtle" type="submit">Verify</button></form><% } %>
<% if (access.can_edit) { %><form method="post" action="/plugins/lumi_ai/improvement_center/corrections/<%= entry.id %>"><input type="hidden" name="action" value="toggle" /><input type="hidden" name="enabled" value="<%= entry.enabled ? "off" : "on" %>" /><button class="button subtle" type="submit"><%= entry.enabled ? "Disable" : "Enable" %></button></form><button class="button subtle" type="button" data-open-dialog="correction-<%= entry.id %>">Edit</button><form method="post" action="/plugins/lumi_ai/improvement_center/corrections/<%= entry.id %>" data-improvement-confirm="Delete this correction?"><input type="hidden" name="action" value="delete" /><button class="button danger" type="submit">Delete</button></form><% } %>
</td>
</tr>
<% if (access.can_edit) { %>
<dialog class="improvement-dialog" id="correction-<%= entry.id %>">
<form method="post" action="/plugins/lumi_ai/improvement_center/corrections/<%= entry.id %>" class="form-grid ai-form">
<input type="hidden" name="action" value="edit" />
<div class="field full"><label>Corrected answer</label><textarea name="corrected_answer" rows="8"><%= entry.corrected_answer %></textarea></div>
<div class="field"><label>Minimum role</label><select name="min_role"><% ["user", "mod", "admin"].forEach((role) => { %><option value="<%= role %>" <%= role === entry.min_role ? "selected" : "" %>><%= role %></option><% }) %></select></div>
<div class="field"><label>Verified expected link</label><input name="expected_link" value="<%= entry.expected_link %>" /></div>
<div class="field"><label>Origin scope</label><input name="permission_origin" value="<%= entry.permission_scope.origin %>" /></div>
<div class="field"><label>Platform scope</label><input name="permission_platform" value="<%= entry.permission_scope.platform %>" /></div>
<div class="field"><label>Route alias</label><input name="route_alias" value="<%= entry.route_alias %>" /></div>
<div class="field"><label><input type="checkbox" name="enabled" <%= entry.enabled ? "checked" : "" %> /> Enabled</label></div>
<div class="field"><label><input type="checkbox" name="explicitly_safe" <%= entry.explicitly_safe ? "checked" : "" %> /> Safe for predefined answer</label></div>
<div class="field full improvement-actions"><button class="button" type="submit">Stage changes</button><button class="button subtle" type="button" data-close-dialog>Cancel</button></div>
</form>
</dialog>
<% } %>
<% }) %>
<% if (!corrections.entries.length) { %><tr><td colspan="5">No corrections have been promoted.</td></tr><% } %>
</tbody>
</table>
</div>
<div class="table-pagination">
<a class="button subtle <%= corrections.page <= 1 ? "disabled" : "" %>" href="?correction_page=<%= Math.max(1, corrections.page - 1) %>#corrections">Previous</a>
<span class="table-page-label">Page <%= corrections.page %> of <%= corrections.pages %> (<%= corrections.total %> corrections)</span>
<a class="button subtle <%= corrections.page >= corrections.pages ? "disabled" : "" %>" href="?correction_page=<%= Math.min(corrections.pages, corrections.page + 1) %>#corrections">Next</a>
</div>
</section>
<section class="ai-band" id="evals">
<div class="ai-section-heading">
<div><h2>Evals</h2><p>Stored cases can be run manually against the current Lumi AI configuration.</p></div>
<% if (access.can_run_evals) { %><form method="post" action="/plugins/lumi_ai/improvement_center/evals/run"><button class="button" type="submit">Run all evals</button></form><% } %>
</div>
<% if (access.can_run_evals) { %>
<details class="ai-settings-group"><summary>Add eval case</summary>
<form method="post" action="/plugins/lumi_ai/improvement_center/evals" class="form-grid ai-form">
<div class="field full"><label>Prompt</label><textarea name="prompt" rows="3" required></textarea></div>
<div class="field"><label>Role</label><select name="role"><option>user</option><option>mod</option><option>admin</option></select></div>
<div class="field"><label>Origin</label><input name="origin" value="webui" /></div>
<div class="field full"><label>Expected behavior</label><textarea name="expected_behavior" rows="3"></textarea></div>
<div class="field full"><label>Forbidden behavior</label><textarea name="forbidden_behavior" rows="3"></textarea></div>
<div class="field"><label>Expected verified link</label><input name="expected_link" /></div>
<div class="field"><label>Notes</label><input name="notes" /></div>
<div class="field full"><button class="button" type="submit">Add eval</button></div>
</form>
</details>
<% } %>
<div class="table-wrap"><table class="table"><thead><tr><th>Prompt</th><th>Role / origin</th><th>Expected</th><th>Forbidden</th><th>Expected link</th><th>Actions</th></tr></thead><tbody>
<% evalCases.entries.forEach((entry) => { %><tr><td><%= entry.prompt %></td><td><%= entry.role %> / <%= entry.origin %></td><td><%= entry.expected_behavior || "-" %></td><td><%= entry.forbidden_behavior || "-" %></td><td><%= entry.expected_link || "-" %></td><td><% if (access.can_run_evals) { %><form method="post" action="/plugins/lumi_ai/improvement_center/evals/<%= entry.id %>/delete" data-improvement-confirm="Delete this eval case?"><button class="button danger" type="submit">Delete</button></form><% } %></td></tr><% }) %>
<% if (!evalCases.entries.length) { %><tr><td colspan="6">No eval cases.</td></tr><% } %>
</tbody></table></div>
<div class="table-pagination">
<a class="button subtle <%= evalCases.page <= 1 ? "disabled" : "" %>" href="?eval_page=<%= Math.max(1, evalCases.page - 1) %>#evals">Previous</a>
<span class="table-page-label">Page <%= evalCases.page %> of <%= evalCases.pages %> (<%= evalCases.total %> cases)</span>
<a class="button subtle <%= evalCases.page >= evalCases.pages ? "disabled" : "" %>" href="?eval_page=<%= Math.min(evalCases.pages, evalCases.page + 1) %>#evals">Next</a>
</div>
<details class="ai-settings-group"><summary>Recent eval results</summary>
<div class="table-wrap"><table class="table"><thead><tr><th>Time</th><th>Case</th><th>Result</th><th>Notes</th></tr></thead><tbody>
<% evalResults.forEach((result) => { %><tr><td><%= formatDate(result.run_at) %></td><td><%= result.case_id %></td><td><span class="ai-tag"><%= result.status %></span></td><td><%= result.notes || "-" %></td></tr><% }) %>
<% if (!evalResults.length) { %><tr><td colspan="4">No eval results.</td></tr><% } %>
</tbody></table></div>
</details>
</section>
<% if (access.can_export) { %>
<section class="ai-band" id="exports">
<div class="ai-section-heading"><div><h2>Training exports</h2><p>Manual JSONL exports include approved examples only. Lumi does not start training.</p></div></div>
<div class="improvement-actions">
<form method="post" action="/plugins/lumi_ai/improvement_center/exports/instruction"><button class="button" type="submit">Export instruction JSONL</button></form>
<form method="post" action="/plugins/lumi_ai/improvement_center/exports/dpo"><button class="button subtle" type="submit">Export DPO JSONL</button></form>
</div>
</section>
<% } %>
<script src="/plugins/lumi_ai/assets/improvement-center.js?v=0.6.0" defer></script>
<%- include("../../../src/web/views/partials/layout-bottom") %>