136 lines
6.5 KiB
TypeScript
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|