// 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.

Respond

Reply

Tools

Conversation memory

healthz

info

`; 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, });