// panes/slack.js — Slack sidecar with respond form, presets, tools.
import { registerPane } from '../app.js';
import { jget, jpost, escapeHtml } from '../lib/api.js';
import { fmtJSON, err, ok, copyToClipboard } from '../lib/ui.js';
const TPL = `
Slack
Sidecar health, conversation memory, ad-hoc responses.
—
Tools
—
…
Conversation memory
…
`;
const PRESETS = {
standup: 'Daily standup: ✅ Yesterday — ___ · 🎯 Today — ___ · 🚧 Blockers — none',
bug: 'Bug report: **Title** — short summary\n\n**Steps to reproduce:**\n1. ___\n2. ___\n\n**Expected:** ___\n**Actual:** ___\n**Env:** ___',
ship: '🚀 Shipped: **** — short summary. Try it at . Feedback welcome 🙏',
};
async function refresh(host) {
try {
const [h, info, mem, tools] = await Promise.all([
jget('/api/slack/healthz').catch(e => ({ error: e.message })),
jget('/api/slack/info').catch(e => ({ error: e.message })),
jget('/api/slack/memory').catch(e => ({ error: e.message })),
jget('/api/slack/tools').catch(e => ({ error: e.message })),
]);
host.querySelector('#slack-health').textContent = fmtJSON(h);
host.querySelector('#slack-info').textContent = fmtJSON(info);
host.querySelector('#slack-memory').textContent = fmtJSON(mem);
const items = tools.tools || [];
host.querySelector('#slack-tools-meta').textContent = `${items.length} tools`;
host.querySelector('#slack-tools-list').innerHTML = items.length
? items.map(t => `
${escapeHtml(t.name)}
${escapeHtml(t.description || '')}
`).join('')
: '(none)
';
const ok2 = !h?.error;
const pill = host.querySelector('#slack-pill');
pill.classList.remove('ok', 'err'); pill.classList.add(ok2 ? 'ok' : 'err');
host.querySelector('#slack-pill-text').textContent = ok2 ? 'up' : 'down';
} catch (e) {
host.querySelector('#slack-health').textContent = e.message;
}
}
registerPane('slack', {
label: 'Slack',
init(host) {
host.innerHTML = TPL;
host.querySelector('#slack-refresh').addEventListener('click', () => refresh(host));
host.querySelectorAll('[data-preset]').forEach(b => b.addEventListener('click', () => {
host.querySelector('#slack-text').value = PRESETS[b.dataset.preset] || '';
}));
host.querySelector('#slack-copy').addEventListener('click', () => copyToClipboard(host.querySelector('#slack-reply').textContent));
host.querySelector('#slack-mem-copy').addEventListener('click', () => copyToClipboard(host.querySelector('#slack-memory').textContent));
host.querySelector('#slack-form').addEventListener('submit', async (e) => {
e.preventDefault();
const body = {
text: host.querySelector('#slack-text').value,
channel: host.querySelector('#slack-channel').value || 'rest',
thread_ts: host.querySelector('#slack-thread').value || 'rest',
};
host.querySelector('#slack-reply').textContent = 'thinking…';
host.querySelector('#slack-resp-meta').textContent = '…';
const t0 = performance.now();
try {
const r = await jpost('/api/slack/respond', body);
host.querySelector('#slack-reply').textContent = r.reply || fmtJSON(r);
host.querySelector('#slack-resp-meta').textContent = `${(performance.now() - t0).toFixed(0)}ms`;
ok('replied');
} catch (e) {
host.querySelector('#slack-reply').textContent = e.body?.detail || e.message;
host.querySelector('#slack-resp-meta').textContent = `error ${e.status ?? ''}`;
err(e.message);
}
});
refresh(host);
},
refresh,
});