185 lines
8.6 KiB
TypeScript
185 lines
8.6 KiB
TypeScript
import { CloseBotClient, err } from "../client.js";
|
|
import type { ToolDefinition, ToolResult, LeadDto, LeadDtoPaginated } from "../types.js";
|
|
|
|
export const tools: ToolDefinition[] = [
|
|
{
|
|
name: "lead_manager_app",
|
|
description: "Searchable lead table with fields, conversation snippets, and status indicators. Returns HTML visualization.",
|
|
inputSchema: {
|
|
type: "object",
|
|
properties: {
|
|
sourceId: { type: "string", description: "Filter by source ID" },
|
|
page: { type: "number", description: "Page number (0-indexed)" },
|
|
pageSize: { type: "number", description: "Page size (default 20, max 100)" },
|
|
leadId: { type: "string", description: "Show details for a specific lead" },
|
|
},
|
|
},
|
|
},
|
|
];
|
|
|
|
function escapeHtml(s: string): string {
|
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
}
|
|
|
|
function renderLeadTable(data: LeadDtoPaginated): string {
|
|
const leads = data.results || [];
|
|
const rows = leads
|
|
.map((lead) => {
|
|
const lastMsg = lead.lastMessage
|
|
? escapeHtml(lead.lastMessage.slice(0, 50))
|
|
: '<span style="color:#555;">—</span>';
|
|
const direction =
|
|
lead.lastMessageDirection === "outbound"
|
|
? '<span style="color:#4ecdc4;">→ out</span>'
|
|
: lead.lastMessageDirection === "inbound"
|
|
? '<span style="color:#ff9f43;">← in</span>'
|
|
: '<span style="color:#555;">—</span>';
|
|
const time = lead.lastMessageTime
|
|
? new Date(lead.lastMessageTime).toLocaleString()
|
|
: "—";
|
|
const source = lead.source?.name || "—";
|
|
const fieldCount = lead.fields?.length || 0;
|
|
const instanceCount = lead.instances?.length || 0;
|
|
const failReason = lead.mostRecentFailureReason
|
|
? `<div style="font-size:10px;color:#ff6b6b;margin-top:2px;">⚠️ ${escapeHtml(lead.mostRecentFailureReason.slice(0, 40))}</div>`
|
|
: "";
|
|
const tags =
|
|
lead.tags && lead.tags.length > 0
|
|
? lead.tags
|
|
.slice(0, 3)
|
|
.map((t) => `<span style="background:#2a1a3e;padding:1px 6px;border-radius:4px;font-size:10px;">${escapeHtml(t)}</span>`)
|
|
.join(" ")
|
|
: "";
|
|
|
|
return `
|
|
<tr style="border-bottom:1px solid #222;">
|
|
<td style="padding:10px 8px;">
|
|
<div style="font-weight:600;font-size:13px;">${escapeHtml(lead.name || "Unknown")}</div>
|
|
<div style="font-size:10px;color:#555;font-family:monospace;">${escapeHtml(lead.id || "")}</div>
|
|
${tags ? `<div style="margin-top:4px;">${tags}</div>` : ""}
|
|
</td>
|
|
<td style="padding:10px 8px;font-size:12px;color:#888;">${escapeHtml(source)}</td>
|
|
<td style="padding:10px 8px;font-size:12px;">
|
|
${direction}
|
|
<div style="font-size:11px;color:#aaa;margin-top:2px;">${lastMsg}</div>
|
|
${failReason}
|
|
</td>
|
|
<td style="padding:10px 8px;font-size:11px;color:#666;">${escapeHtml(time)}</td>
|
|
<td style="padding:10px 8px;text-align:center;font-size:12px;">${fieldCount}</td>
|
|
<td style="padding:10px 8px;text-align:center;font-size:12px;">${instanceCount}</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;">👥 Lead Manager</h2>
|
|
<div style="font-size:12px;color:#666;margin-bottom:16px;">
|
|
${data.total ?? leads.length} total leads · Page ${(data.page ?? 0) + 1} · ${data.pageSize ?? 20} per page
|
|
</div>
|
|
<div style="overflow-x:auto;">
|
|
<table style="width:100%;border-collapse:collapse;">
|
|
<thead>
|
|
<tr style="background:#1a1a2e;">
|
|
<th style="text-align:left;padding:10px 8px;font-size:12px;color:#888;">Lead</th>
|
|
<th style="text-align:left;padding:10px 8px;font-size:12px;color:#888;">Source</th>
|
|
<th style="text-align:left;padding:10px 8px;font-size:12px;color:#888;">Last Message</th>
|
|
<th style="text-align:left;padding:10px 8px;font-size:12px;color:#888;">Time</th>
|
|
<th style="text-align:center;padding:10px 8px;font-size:12px;color:#888;">Fields</th>
|
|
<th style="text-align:center;padding:10px 8px;font-size:12px;color:#888;">Bots</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
${rows || '<tr><td colspan="6" style="padding:40px;text-align:center;color:#666;">No leads found</td></tr>'}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
function renderLeadDetail(lead: LeadDto): string {
|
|
const fields = (lead.fields || [])
|
|
.map(
|
|
(f) =>
|
|
`<tr><td style="padding:4px 8px;color:#888;font-size:12px;">${escapeHtml(f.field || "")}</td><td style="padding:4px 8px;font-size:12px;">${escapeHtml(f.value || "—")}</td></tr>`
|
|
)
|
|
.join("");
|
|
|
|
const instances = (lead.instances || [])
|
|
.map(
|
|
(i) =>
|
|
`<tr><td style="padding:4px 8px;font-size:12px;font-family:monospace;">${escapeHtml(i.botId || "")}</td><td style="padding:4px 8px;font-size:12px;">v${escapeHtml(i.botVersion || "?")}</td><td style="padding:4px 8px;font-size:12px;">${i.followUpTime ? new Date(i.followUpTime).toLocaleString() : "—"}</td></tr>`
|
|
)
|
|
.join("");
|
|
|
|
const tags = (lead.tags || [])
|
|
.map((t) => `<span style="background:#2a1a3e;padding:2px 8px;border-radius:6px;font-size:11px;margin:2px;">${escapeHtml(t)}</span>`)
|
|
.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(lead.name || "Unknown Lead")}</h2>
|
|
<div style="font-size:12px;color:#555;font-family:monospace;margin-bottom:16px;">${escapeHtml(lead.id || "")}</div>
|
|
|
|
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:20px;">
|
|
<div style="background:#1a1a2e;padding:12px;border-radius:8px;">
|
|
<div style="font-size:11px;color:#888;">Source</div>
|
|
<div style="font-size:14px;margin-top:4px;">${escapeHtml(lead.source?.name || "—")}</div>
|
|
</div>
|
|
<div style="background:#1a1a2e;padding:12px;border-radius:8px;">
|
|
<div style="font-size:11px;color:#888;">Last Message</div>
|
|
<div style="font-size:12px;margin-top:4px;color:#aaa;">${escapeHtml((lead.lastMessage || "—").slice(0, 80))}</div>
|
|
</div>
|
|
<div style="background:#1a1a2e;padding:12px;border-radius:8px;">
|
|
<div style="font-size:11px;color:#888;">Contact ID</div>
|
|
<div style="font-size:12px;margin-top:4px;font-family:monospace;">${escapeHtml(lead.contactId || "—")}</div>
|
|
</div>
|
|
</div>
|
|
|
|
${tags ? `<div style="margin-bottom:16px;">${tags}</div>` : ""}
|
|
|
|
${lead.mostRecentFailureReason ? `<div style="background:#2a1a1a;border:1px solid #5a2a2a;border-radius:8px;padding:12px;margin-bottom:16px;font-size:12px;color:#ff6b6b;">⚠️ ${escapeHtml(lead.mostRecentFailureReason)}</div>` : ""}
|
|
|
|
<h3 style="color:#ccc;margin:16px 0 8px;">📋 Fields (${lead.fields?.length || 0})</h3>
|
|
<table style="width:100%;border-collapse:collapse;">
|
|
${fields || '<tr><td style="padding:8px;color:#666;">No fields</td></tr>'}
|
|
</table>
|
|
|
|
<h3 style="color:#ccc;margin:16px 0 8px;">🤖 Bot Instances (${lead.instances?.length || 0})</h3>
|
|
<table style="width:100%;border-collapse:collapse;">
|
|
<tr style="background:#1a1a2e;"><th style="text-align:left;padding:6px 8px;font-size:11px;">Bot ID</th><th>Version</th><th>Follow-up</th></tr>
|
|
${instances || '<tr><td colspan="3" style="padding:8px;color:#666;">No instances</td></tr>'}
|
|
</table>
|
|
</div>`;
|
|
}
|
|
|
|
export async function handler(
|
|
client: CloseBotClient,
|
|
name: string,
|
|
args: Record<string, unknown>
|
|
): Promise<ToolResult> {
|
|
try {
|
|
if (args.leadId) {
|
|
const lead = await client.get<LeadDto>(`/lead/${args.leadId}`);
|
|
const html = renderLeadDetail(lead);
|
|
return {
|
|
content: [{ type: "text", text: `Lead details: ${lead.name || lead.id}` }],
|
|
structuredContent: { type: "html", html },
|
|
};
|
|
}
|
|
|
|
const data = await client.get<LeadDtoPaginated>("/lead", {
|
|
page: args.page,
|
|
pageSize: args.pageSize || 20,
|
|
sourceId: args.sourceId,
|
|
});
|
|
const html = renderLeadTable(data);
|
|
return {
|
|
content: [{ type: "text", text: `${data.total ?? (data.results?.length || 0)} leads found` }],
|
|
structuredContent: { type: "html", html },
|
|
};
|
|
} catch (error) {
|
|
return err(error);
|
|
}
|
|
}
|