import { CloseBotClient, err } from "../client.js"; import type { ToolDefinition, ToolResult, FileDto } from "../types.js"; export const tools: ToolDefinition[] = [ { name: "library_manager_app", description: "Knowledge base library viewer showing files with type icons, source attachments, scrape status, and size. Returns HTML visualization.", inputSchema: { type: "object", properties: { fileId: { type: "string", description: "Optional: show detail for a specific file including scrape pages" }, }, }, }, ]; function escapeHtml(s: string): string { return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } function fileIcon(fileType: string | null | undefined): string { const t = (fileType || "").toLowerCase(); if (t.includes("pdf")) return "๐Ÿ“„"; if (t.includes("doc") || t.includes("word")) return "๐Ÿ“"; if (t.includes("csv") || t.includes("excel") || t.includes("spreadsheet")) return "๐Ÿ“Š"; if (t.includes("image") || t.includes("png") || t.includes("jpg")) return "๐Ÿ–ผ๏ธ"; if (t.includes("webscrape") || t.includes("web") || t.includes("html")) return "๐ŸŒ"; if (t.includes("text") || t.includes("txt")) return "๐Ÿ“ƒ"; if (t.includes("json")) return "๐Ÿ”ง"; return "๐Ÿ“"; } function formatSize(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } function statusBadge(status: string | null | undefined): string { const s = (status || "").toLowerCase(); if (s === "ready" || s === "completed" || s === "processed") return 'โœ… Ready'; if (s === "processing" || s === "pending") return 'โณ Processing'; if (s === "error" || s === "failed") return 'โŒ Error'; return `${escapeHtml(status || "Unknown")}`; } function renderFileList(files: FileDto[]): string { const totalSize = files.reduce((sum, f) => sum + (f.fileSize || 0), 0); const rows = files .map((f) => { const icon = fileIcon(f.fileType); const sources = (f.sources || []) .map( (s) => `${escapeHtml(s.name || s.id || "")}` ) .join(" "); const modified = f.lastModified ? new Date(f.lastModified).toLocaleDateString() : "โ€”"; return `
${icon}
${escapeHtml(f.fileName || "Unnamed")}
${statusBadge(f.fileStatus)} ${escapeHtml(f.fileType || "")} ${formatSize(f.fileSize || 0)} ${escapeHtml(modified)}
${sources ? `
${sources}
` : ""}
${escapeHtml(f.fileId || "")}
`; }) .join(""); return `

๐Ÿ“š Library Manager

${files.length} files ยท ${formatSize(totalSize)} total
${rows || '
No files. Use upload_file or create_web_scrape to add content.
'}
๐Ÿ’ก Use upload_file to upload, create_web_scrape to scrape websites, attach_file_to_source to connect to sources
`; } function renderFileDetail(file: FileDto, scrapePages: Array<{ url?: string; enabled?: boolean }>): string { const sources = (file.sources || []) .map( (s) => `
${escapeHtml(s.name || "Unnamed")} ${escapeHtml(s.category || "")} ยท ${escapeHtml(s.id || "")}
` ) .join(""); const pages = scrapePages .map( (p) => `
${p.enabled ? "โœ…" : "โŒ"} ${escapeHtml(p.url || "")}
` ) .join(""); return `
${fileIcon(file.fileType)}

${escapeHtml(file.fileName || "Unnamed")}

${escapeHtml(file.fileId || "")}
Status
${statusBadge(file.fileStatus)}
Type
${escapeHtml(file.fileType || "โ€”")}
Size
${formatSize(file.fileSize || 0)}
Modified
${file.lastModified ? new Date(file.lastModified).toLocaleDateString() : "โ€”"}

๐Ÿ“ก Attached Sources (${file.sources?.length || 0})

${sources || '
Not attached to any sources
'}
${scrapePages.length > 0 ? `

๐ŸŒ Scrape Pages (${scrapePages.length})

${pages}
` : ""}
`; } export async function handler( client: CloseBotClient, name: string, args: Record ): Promise { try { if (args.fileId) { const [file, scrapePages] = await Promise.all([ client.get(`/library/files/${args.fileId}`), client.get>(`/library/files/${args.fileId}/scrape-pages`).catch(() => []), ]); const html = renderFileDetail(file, scrapePages); return { content: [{ type: "text", text: `File: ${file.fileName} (${file.fileType})` }], structuredContent: { type: "html", html }, }; } const files = await client.get("/library/files"); const html = renderFileList(files); return { content: [{ type: "text", text: `Library: ${files.length} files` }], structuredContent: { type: "html", html }, }; } catch (error) { return err(error); } }