136 lines
6.5 KiB
TypeScript

import { CloseBotClient, err } from "../client.js";
import type { ToolDefinition, ToolResult, BotDto } from "../types.js";
export const tools: ToolDefinition[] = [
{
name: "bot_dashboard_app",
description: "Rich dashboard showing all bots in a grid with status, versions, source count, and details. Returns HTML visualization.",
inputSchema: {
type: "object",
properties: {
botId: { type: "string", description: "Optional: show details for a specific bot instead of the grid" },
},
},
},
];
function escapeHtml(s: string): string {
return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
}
function renderBotGrid(bots: BotDto[]): string {
const botCards = bots.map((bot) => {
const latestVersion = bot.versions?.find((v) => v.published) || bot.versions?.[0];
const versionLabel = latestVersion?.version || "draft";
const published = latestVersion?.published ? "🟢" : "🟡";
const sourceCount = bot.sources?.length || 0;
const locked = bot.locked ? "🔒" : "";
const fav = bot.favorited ? "⭐" : "";
const category = bot.category || "—";
const modified = bot.modifiedAt ? new Date(bot.modifiedAt).toLocaleDateString() : "—";
return `
<div style="background:#1a1a2e;border:1px solid #333;border-radius:12px;padding:16px;display:flex;flex-direction:column;gap:8px;">
<div style="display:flex;justify-content:space-between;align-items:center;">
<span style="font-weight:600;font-size:15px;color:#e0e0e0;">${fav} ${escapeHtml(bot.name || "Unnamed")} ${locked}</span>
<span style="font-size:12px;color:#888;">${escapeHtml(category)}</span>
</div>
<div style="display:flex;gap:12px;font-size:13px;color:#aaa;">
<span>${published} v${escapeHtml(versionLabel)}</span>
<span>📡 ${sourceCount} source${sourceCount !== 1 ? "s" : ""}</span>
</div>
<div style="font-size:12px;color:#666;">Modified: ${escapeHtml(modified)}</div>
<div style="font-size:11px;color:#555;font-family:monospace;">ID: ${escapeHtml(bot.id || "")}</div>
${bot.followUpActive ? '<div style="font-size:11px;color:#4ecdc4;">↻ Follow-ups active</div>' : ""}
${bot.tools && bot.tools.length > 0 ? `<div style="font-size:11px;color:#8888ff;">🔧 ${bot.tools.length} tool(s)</div>` : ""}
</div>`;
}).join("");
return `
<div style="font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#0d0d1a;color:#e0e0e0;padding:20px;border-radius:16px;">
<h2 style="margin:0 0 16px;color:#fff;">🤖 Bot Dashboard <span style="font-size:14px;color:#888;">(${bots.length} bots)</span></h2>
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;">
${botCards}
</div>
</div>`;
}
function renderBotDetail(bot: BotDto): string {
const versions = (bot.versions || [])
.map(
(v) =>
`<tr><td style="padding:4px 12px;">${escapeHtml(v.version || "")}</td><td>${v.published ? "✅ Published" : "📝 Draft"}</td><td>${escapeHtml(v.name || "—")}</td><td>${v.modifiedAt ? new Date(v.modifiedAt).toLocaleString() : "—"}</td></tr>`
)
.join("");
const sources = (bot.sources || [])
.map(
(s) =>
`<tr><td style="padding:4px 12px;">${escapeHtml(s.name || "Unnamed")}</td><td>${escapeHtml(s.category || "—")}</td><td>${s.enabled ? "✅" : "❌"}</td><td style="font-family:monospace;font-size:11px;">${escapeHtml(s.id || "")}</td></tr>`
)
.join("");
return `
<div style="font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#0d0d1a;color:#e0e0e0;padding:20px;border-radius:16px;">
<h2 style="margin:0 0 4px;color:#fff;">🤖 ${escapeHtml(bot.name || "Unnamed")}</h2>
<div style="font-size:12px;color:#666;margin-bottom:16px;font-family:monospace;">ID: ${escapeHtml(bot.id || "")}</div>
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:20px;">
<div style="background:#1a1a2e;padding:12px;border-radius:8px;text-align:center;">
<div style="font-size:24px;">${bot.sources?.length || 0}</div>
<div style="font-size:12px;color:#888;">Sources</div>
</div>
<div style="background:#1a1a2e;padding:12px;border-radius:8px;text-align:center;">
<div style="font-size:24px;">${bot.versions?.length || 0}</div>
<div style="font-size:12px;color:#888;">Versions</div>
</div>
<div style="background:#1a1a2e;padding:12px;border-radius:8px;text-align:center;">
<div style="font-size:24px;">${bot.locked ? "🔒" : "🔓"}</div>
<div style="font-size:12px;color:#888;">${bot.locked ? "Locked" : "Unlocked"}</div>
</div>
<div style="background:#1a1a2e;padding:12px;border-radius:8px;text-align:center;">
<div style="font-size:24px;">${bot.followUpActive ? "✅" : "❌"}</div>
<div style="font-size:12px;color:#888;">Follow-ups</div>
</div>
</div>
<h3 style="color:#ccc;margin:16px 0 8px;">📋 Versions</h3>
<table style="width:100%;border-collapse:collapse;font-size:13px;">
<tr style="background:#1a1a2e;"><th style="text-align:left;padding:8px 12px;">Version</th><th>Status</th><th>Name</th><th>Modified</th></tr>
${versions || '<tr><td colspan="4" style="padding:8px;color:#666;">No versions</td></tr>'}
</table>
<h3 style="color:#ccc;margin:16px 0 8px;">📡 Sources</h3>
<table style="width:100%;border-collapse:collapse;font-size:13px;">
<tr style="background:#1a1a2e;"><th style="text-align:left;padding:8px 12px;">Name</th><th>Category</th><th>Enabled</th><th>ID</th></tr>
${sources || '<tr><td colspan="4" style="padding:8px;color:#666;">No sources attached</td></tr>'}
</table>
</div>`;
}
export async function handler(
client: CloseBotClient,
name: string,
args: Record<string, unknown>
): Promise<ToolResult> {
try {
if (args.botId) {
const bot = await client.get<BotDto>(`/bot/${args.botId}`);
const html = renderBotDetail(bot);
return {
content: [{ type: "text", text: `Bot details for ${bot.name || bot.id}` }],
structuredContent: { type: "html", html },
};
}
const bots = await client.get<BotDto[]>("/bot");
const html = renderBotGrid(bots);
return {
content: [{ type: "text", text: `Dashboard showing ${bots.length} bots` }],
structuredContent: { type: "html", html },
};
} catch (error) {
return err(error);
}
}