/** * MI Triage Panel β€” floating preview UI for content triage proposals. * * Shows the AI summary, proposed shapes as cards with remove buttons, * connections, and Create All / Cancel actions. * Uses existing --rs-* CSS variables for theming. */ import type { TriageManager } from "./mi-content-triage"; /** Icon lookup by tagName β€” matches TOOL_HINTS from mi-tool-schema.ts */ const SHAPE_ICONS: Record = { "folk-markdown": { icon: "πŸ“", label: "Note" }, "folk-embed": { icon: "πŸ”—", label: "Embed" }, "folk-calendar": { icon: "πŸ“…", label: "Calendar" }, "folk-map": { icon: "πŸ—ΊοΈ", label: "Map" }, "folk-workflow-block": { icon: "βš™οΈ", label: "Workflow" }, "folk-social-post": { icon: "πŸ“£", label: "Social Post" }, "folk-choice-vote": { icon: "πŸ—³οΈ", label: "Vote" }, "folk-prompt": { icon: "πŸ€–", label: "AI Chat" }, "folk-image-gen": { icon: "🎨", label: "AI Image" }, "folk-slide": { icon: "πŸ–ΌοΈ", label: "Slide" }, }; export class MiTriagePanel { private el: HTMLDivElement; private manager: TriageManager; constructor(manager: TriageManager) { this.manager = manager; this.el = document.createElement("div"); this.el.className = "mi-triage-panel"; this.el.innerHTML = this.renderLoading(); this.injectStyles(); document.body.appendChild(this.el); // Re-render when manager state changes this.manager = manager; const originalOnChange = (manager as any).onChange; (manager as any).onChange = () => { originalOnChange?.(); this.render(); }; } private render() { switch (this.manager.status) { case "analyzing": this.el.innerHTML = this.renderLoading(); break; case "ready": this.el.innerHTML = this.renderProposal(); this.bindEvents(); break; case "error": this.el.innerHTML = this.renderError(); this.bindCloseEvent(); break; default: this.close(); } } private renderLoading(): string { return `
MI Content Triage

Analyzing content...

`; } private renderError(): string { return `
MI Content Triage

${this.manager.error || "Something went wrong"}

`; } private renderProposal(): string { const p = this.manager.proposal!; const shapeCards = p.shapes .map((s, i) => { const info = SHAPE_ICONS[s.tagName] || { icon: "πŸ“¦", label: s.tagName }; return `
${info.icon} ${escapeHtml(s.label)} ${info.label}
${escapeHtml(s.snippet || "")}
`; }) .join(""); const connList = p.connections.length > 0 ? `
Connections
${p.connections .map((c) => { const from = p.shapes[c.fromIndex]?.label || `#${c.fromIndex}`; const to = p.shapes[c.toIndex]?.label || `#${c.toIndex}`; return `
${escapeHtml(from)} β†’ ${escapeHtml(to)}${escapeHtml(c.reason || "")}
`; }) .join("")}
` : ""; return `
MI Content Triage
${escapeHtml(p.summary)}
${shapeCards} ${connList}
`; } private bindEvents() { this.el.querySelectorAll("[data-action='remove']").forEach((btn) => { btn.addEventListener("click", (e) => { const idx = parseInt((e.currentTarget as HTMLElement).dataset.index || "0"); this.manager.removeShape(idx); }); }); this.el.querySelector("[data-action='commit']")?.addEventListener("click", () => { this.manager.commitAll(); this.close(); }); this.bindCloseEvent(); } private bindCloseEvent() { this.el.querySelectorAll("[data-action='close']").forEach((btn) => { btn.addEventListener("click", () => this.close()); }); } close() { this.el.remove(); } private injectStyles() { if (document.getElementById("mi-triage-styles")) return; const style = document.createElement("style"); style.id = "mi-triage-styles"; style.textContent = ` .mi-triage-panel { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 420px; max-width: 90vw; max-height: 80vh; display: flex; flex-direction: column; background: var(--rs-surface, #1e1e2e); color: var(--rs-text, #cdd6f4); border: 1px solid var(--rs-border, #45475a); border-radius: 16px; box-shadow: 0 20px 60px rgba(0,0,0,0.4); z-index: 10000; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 13px; animation: mi-triage-in 0.2s ease-out; } @keyframes mi-triage-in { from { opacity: 0; transform: translate(-50%, -48%); } to { opacity: 1; transform: translate(-50%, -50%); } } .mi-triage-header { display: flex; align-items: center; justify-content: space-between; padding: 14px 16px 10px; border-bottom: 1px solid var(--rs-border, #45475a); } .mi-triage-title { font-weight: 600; font-size: 14px; } .mi-triage-close { background: none; border: none; color: var(--rs-text-muted, #a6adc8); font-size: 18px; cursor: pointer; padding: 0 4px; line-height: 1; } .mi-triage-close:hover { color: var(--rs-text, #cdd6f4); } .mi-triage-summary { padding: 10px 16px; font-size: 12px; color: var(--rs-text-muted, #a6adc8); border-bottom: 1px solid var(--rs-border, #45475a); } .mi-triage-body { flex: 1; overflow-y: auto; padding: 12px; display: flex; flex-direction: column; gap: 8px; } .mi-triage-loading { align-items: center; justify-content: center; padding: 40px 16px; } .mi-triage-spinner { width: 28px; height: 28px; border: 3px solid var(--rs-border, #45475a); border-top-color: #14b8a6; border-radius: 50%; animation: mi-spin 0.8s linear infinite; margin-bottom: 12px; } @keyframes mi-spin { to { transform: rotate(360deg); } } .mi-triage-error { align-items: center; justify-content: center; padding: 32px 16px; color: #f38ba8; } .mi-triage-card { background: var(--rs-card-bg, #313244); border: 1px solid var(--rs-border, #45475a); border-radius: 10px; padding: 10px 12px; } .mi-triage-card-header { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; } .mi-triage-card-icon { font-size: 16px; } .mi-triage-card-label { flex: 1; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .mi-triage-card-badge { font-size: 10px; padding: 2px 6px; border-radius: 6px; background: var(--rs-badge-bg, #45475a); color: var(--rs-text-muted, #a6adc8); white-space: nowrap; } .mi-triage-card-remove { background: none; border: none; color: var(--rs-text-muted, #a6adc8); font-size: 16px; cursor: pointer; padding: 0 2px; line-height: 1; } .mi-triage-card-remove:hover { color: #f38ba8; } .mi-triage-card-snippet { font-size: 11px; color: var(--rs-text-muted, #a6adc8); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .mi-triage-connections { padding: 8px 0 0; border-top: 1px solid var(--rs-border, #45475a); } .mi-triage-conn-title { font-size: 11px; font-weight: 600; color: var(--rs-text-muted, #a6adc8); margin-bottom: 4px; } .mi-triage-conn { font-size: 11px; padding: 2px 0; color: var(--rs-text, #cdd6f4); } .mi-triage-conn-reason { margin-left: 6px; color: var(--rs-text-muted, #a6adc8); } .mi-triage-footer { display: flex; justify-content: flex-end; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--rs-border, #45475a); } .mi-triage-btn { padding: 7px 16px; border: none; border-radius: 8px; font-size: 13px; cursor: pointer; font-weight: 500; } .mi-triage-btn-cancel { background: var(--rs-card-bg, #313244); color: var(--rs-text, #cdd6f4); } .mi-triage-btn-cancel:hover { background: var(--rs-border, #45475a); } .mi-triage-btn-commit { background: #14b8a6; color: white; } .mi-triage-btn-commit:hover { background: #0d9488; } `; document.head.appendChild(style); } } function escapeHtml(s: string): string { return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); }