polish: favicon, markdown rendering, steward inline login form, co-steward support

This commit is contained in:
gitadmin 2026-05-02 14:13:28 +00:00
parent 0bfdb2285e
commit 0755a83283

View file

@ -3028,6 +3028,8 @@ a{color:#c9b87e}a:hover{text-decoration:underline}
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"> <meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>${esc(name)} Agentify.Help</title> <title>${esc(name)} Agentify.Help</title>
<meta name="description" content="${metaDesc}"> <meta name="description" content="${metaDesc}">
<link rel="icon" href="${AH_FAVICON}">
<script src="https://cdn.jsdelivr.net/npm/marked@9/marked.min.js"></script>
<style> <style>
*{box-sizing:border-box;margin:0;padding:0} *{box-sizing:border-box;margin:0;padding:0}
body{background:#07102a;color:#d4cfbb;font-family:"IBM Plex Sans","Inter",system-ui,sans-serif;min-height:100vh} body{background:#07102a;color:#d4cfbb;font-family:"IBM Plex Sans","Inter",system-ui,sans-serif;min-height:100vh}
@ -3046,7 +3048,18 @@ h1{font-size:1.6rem;color:#f0e8d0;margin-bottom:4px}
.chat-box{width:100%;min-height:80px;background:#060e1c;border:1px solid #1a2e44;border-radius:6px;padding:12px;color:#d4cfbb;font-family:inherit;font-size:.88rem;resize:vertical} .chat-box{width:100%;min-height:80px;background:#060e1c;border:1px solid #1a2e44;border-radius:6px;padding:12px;color:#d4cfbb;font-family:inherit;font-size:.88rem;resize:vertical}
.btn{padding:9px 22px;background:#c9b87e;color:#07102a;border:none;border-radius:6px;font-weight:700;font-size:.88rem;cursor:pointer;margin-top:8px} .btn{padding:9px 22px;background:#c9b87e;color:#07102a;border:none;border-radius:6px;font-weight:700;font-size:.88rem;cursor:pointer;margin-top:8px}
.btn:hover{background:#d4c48e} .btn:hover{background:#d4c48e}
.response{background:#060e1c;border:1px solid #1a2e44;border-radius:6px;padding:14px;margin-top:12px;font-size:.88rem;line-height:1.65;white-space:pre-wrap;display:none} .response{background:#060e1c;border:1px solid #1a2e44;border-radius:6px;padding:14px;margin-top:12px;font-size:.88rem;line-height:1.65;display:none}
.response p{margin-bottom:.75em}.response p:last-child{margin-bottom:0}
.response h1,.response h2,.response h3{color:#f0e8d0;margin:1em 0 .4em;font-size:1rem}
.response h2{font-size:.95rem}.response h3{font-size:.9rem}
.response ul,.response ol{padding-left:1.4em;margin-bottom:.75em}
.response li{margin-bottom:.25em}
.response strong{color:#f0e8d0}.response em{color:#c9b87e}
.response code{background:#1a2e4a;padding:1px 5px;border-radius:3px;font-family:monospace;font-size:.83em;color:#86efac}
.response pre{background:#1a2e4a;padding:10px 12px;border-radius:5px;overflow-x:auto;margin-bottom:.75em}
.response pre code{background:none;padding:0;color:#86efac}
.response blockquote{border-left:3px solid #c9b87e44;padding-left:12px;color:#8899bb;margin:.5em 0}
.response hr{border:none;border-top:1px solid #1a2e44;margin:.75em 0}
.scope-list{list-style:none;display:flex;flex-wrap:wrap;gap:4px} .scope-list{list-style:none;display:flex;flex-wrap:wrap;gap:4px}
.scope-list li{background:#1a2e4a;color:#c9b87e;padding:2px 10px;border-radius:3px;font-family:monospace;font-size:.72rem} .scope-list li{background:#1a2e4a;color:#c9b87e;padding:2px 10px;border-radius:3px;font-family:monospace;font-size:.72rem}
.refusal-list li{color:#f87171} .refusal-list li{color:#f87171}
@ -3184,9 +3197,12 @@ h1{font-size:1.6rem;color:#f0e8d0;margin-bottom:4px}
<div class="card" style="border-color:#1a2e4a44;margin-top:32px"> <div class="card" style="border-color:#1a2e4a44;margin-top:32px">
<div class="card-title" style="color:#6b7f99">Steward access</div> <div class="card-title" style="color:#6b7f99">Steward access</div>
<p style="font-size:.82rem;color:#6b7f99;margin-bottom:12px">Are you the steward for this expert? Access your corpus portal to review, expand, or update the corpus or check the status of this agent.</p> <p style="font-size:.82rem;color:#6b7f99;margin-bottom:14px">Are you the steward or a collaborator for this agent? Enter your email to receive a secure link to your corpus portal.</p>
${stewardDashToken ? `<a href="/stewardship/${esc(slug)}?token=${esc(stewardDashToken)}" style="display:inline-block;padding:7px 16px;background:#1a2e4a;color:#c9b87e;border:1px solid #c9b87e44;border-radius:6px;font-size:.82rem;font-weight:600;text-decoration:none">Open my corpus portal →</a>` : ""} <div style="display:flex;gap:8px;flex-wrap:wrap;align-items:center">
<a href="/my-agents" style="display:inline-block;padding:7px 16px;background:#0d1f3c;color:#8899bb;border:1px solid #1a2e4a;border-radius:6px;font-size:.82rem;font-weight:600;text-decoration:none;margin-left:8px">Find my agents by email </a> <input type="email" id="steward-email" placeholder="your@email.com" style="flex:1;min-width:200px;background:#060e1c;border:1px solid #1a2e44;border-radius:5px;padding:8px 10px;color:#d4cfbb;font-family:inherit;font-size:.82rem;box-sizing:border-box">
<button onclick="requestPortfolioAccess()" style="padding:8px 16px;background:#1a2e4a;color:#c9b87e;border:1px solid #c9b87e44;border-radius:5px;font-size:.82rem;font-weight:600;cursor:pointer;font-family:inherit;white-space:nowrap">Send my link </button>
</div>
<div id="steward-access-result" style="margin-top:8px;font-size:.8rem;color:#6b7f99"></div>
</div> </div>
</div> </div>
@ -3205,13 +3221,39 @@ async function ask() {
}); });
const d = await r.json(); const d = await r.json();
if (d.error === 'rate_limit') { resp.textContent = 'Rate limit reached — please try again in ' + (d.retry_after || 'a little while') + '.'; return; } if (d.error === 'rate_limit') { resp.textContent = 'Rate limit reached — please try again in ' + (d.retry_after || 'a little while') + '.'; return; }
resp.textContent = d.answer || d.response || d.error || JSON.stringify(d, null, 2); const text = d.answer || d.response || d.error || JSON.stringify(d, null, 2);
resp.innerHTML = (typeof marked !== 'undefined') ? marked.parse(text) : text.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/\n/g,'<br>');
} catch(e) { } catch(e) {
resp.textContent = 'Error: ' + e.message; resp.textContent = 'Error: ' + e.message;
} }
} }
document.getElementById('q')?.addEventListener('keydown', e => { if (e.key === 'Enter' && e.metaKey) ask(); }); document.getElementById('q')?.addEventListener('keydown', e => { if (e.key === 'Enter' && e.metaKey) ask(); });
async function requestPortfolioAccess() {
const emailEl = document.getElementById('steward-email');
const result = document.getElementById('steward-access-result');
const email = emailEl.value.trim();
if (!email || !email.includes('@')) { result.textContent = 'Please enter a valid email address.'; result.style.color = '#f87171'; return; }
result.textContent = 'Sending…'; result.style.color = '#6b7f99';
try {
const r = await fetch('/api/agentify-help/portfolio-access', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email })
});
const d = await r.json();
if (d.ok && d.sent !== false) {
result.textContent = '✓ Check your inbox — your steward link is on the way.';
result.style.color = '#22c55e';
} else {
result.textContent = d.message || 'No steward account found for that email.';
result.style.color = '#f87171';
}
} catch(e) {
result.textContent = 'Error sending link. Please try again.';
result.style.color = '#f87171';
}
}
function toggleSuggest() { function toggleSuggest() {
const body = document.getElementById('suggest-body'); const body = document.getElementById('suggest-body');
const btn = document.getElementById('suggest-toggle-btn'); const btn = document.getElementById('suggest-toggle-btn');
@ -3279,7 +3321,8 @@ async function submitSuggestion() {
try { try {
const { rows } = await pool.query( const { rows } = await pool.query(
`SELECT subject_slug, subject_name, status, dashboard_token FROM agentify_subject_registry `SELECT subject_slug, subject_name, status, dashboard_token FROM agentify_subject_registry
WHERE LOWER(steward_email) = LOWER($1) AND status != 'verify_email' WHERE (LOWER(steward_email) = LOWER($1) OR LOWER($1) = ANY(COALESCE(co_steward_emails, '{}')))
AND status != 'verify_email'
ORDER BY created_at DESC`, [email] ORDER BY created_at DESC`, [email]
); );
if (rows.length === 0) { if (rows.length === 0) {
@ -3288,7 +3331,7 @@ async function submitSuggestion() {
const token = makePortfolioToken(email); const token = makePortfolioToken(email);
const portfolioUrl = `https://agentify.help/my-agents?token=${token}`; const portfolioUrl = `https://agentify.help/my-agents?token=${token}`;
const expertList = rows.map((r: any) => { const expertList = rows.map((r: any) => {
const dashLink = r.dashboard_token ? `<a href="https://agentify.help/stewardship/${r.subject_slug}?token=${r.dashboard_token}" style="color:#c9b87e;font-weight:600">Open corpus portal →</a>` : ""; const dashLink = r.dashboard_token ? `<a href="https://agentify.help/steward/${r.subject_slug}?token=${r.dashboard_token}" style="color:#c9b87e;font-weight:600">Open corpus portal →</a>` : "";
return `<li style="margin-bottom:14px"><strong>${r.subject_name}</strong> <span style="color:#888;font-size:.82rem">(${r.status})</span><br><a href="https://agentify.help/agents/${r.subject_slug}" style="color:#4499ff;font-size:.82rem">View public page</a>${dashLink ? " · " + dashLink : ""}</li>`; return `<li style="margin-bottom:14px"><strong>${r.subject_name}</strong> <span style="color:#888;font-size:.82rem">(${r.status})</span><br><a href="https://agentify.help/agents/${r.subject_slug}" style="color:#4499ff;font-size:.82rem">View public page</a>${dashLink ? " · " + dashLink : ""}</li>`;
}).join(""); }).join("");
await resend.emails.send({ await resend.emails.send({