import React, { useMemo, useState, useEffect } from "react"; // Keep animations minimal to avoid sandbox issues. import { Search, Filter, Copy, Play, Download, Tag, X, Sparkles, Save, Trash2, Upload, FileSpreadsheet, Layers, BookMarked, FileText, FileUp } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Badge } from "@/components/ui/badge"; // XLSX for Excel read/write in-browser import * as XLSX from "xlsx"; // PDF text extraction (no web worker to fit sandbox) import * as pdfjsLib from "pdfjs-dist"; // DOCX → text (browser build) import * as mammoth from "mammoth"; /** * Interactive AI Prompt Library (Searchable, Click-to-Start) * + Quick Export to Excel/PDF * + Saved Prompts * + 📎 Document-to-Variable uploads (PDF/DOCX/TXT) for prompts * * Fix in this revision: * - Remove pdf.js GlobalWorkerOptions.workerSrc assignment. In sandboxed environments, setting it to `undefined` causes * "Invalid `workerSrc` type". We instead rely solely on `{ disableWorker: true }` in all `getDocument` calls. * * Existing features: * - No framer-motion (sandbox-safe transitions only) * - Upload documents into variables (SOPs, policies, etc.) * - Quick Export supports CSV/XLSX/PDF; PDF uses rules to structure lines → rows * - Self-tests for helpers */ // NOTE: Do NOT set pdfjsLib.GlobalWorkerOptions.workerSrc here. We operate with disableWorker: true per call. // ---------- Types ---------- export type Prompt = { id: string; title: string; function: "Operations" | "Customer Service" | "Finance" | "Sales" | "HR" | "General"; stage: "Lightbulb" | "Everyday" | "Strategy" | "Advanced"; tags: string[]; description?: string; template: string; // may include {{variables}} }; export type SavedPrompt = { id: string; // unique id promptId: string; // original template id title: string; // user-provided title filledText: string; // fully materialized prompt text savedAt: number; // epoch ms }; // ---------- Seed Data (edit/expand freely) ---------- const PROMPTS: Prompt[] = [ // Lightbulb (General) { id: "lb-email", title: "Draft or respond to an email", function: "General", stage: "Lightbulb", tags: ["email", "communication", "fast"], description: "Paste an email and get a clear, empathetic reply with next steps.", template: "I just received this email: \n\n---\n{{email_text}}\n---\n\nPlease draft a clear, professional, and empathetic response that addresses the sender’s concerns and proposes specific next steps. Keep it under {{max_words}} words and include a short subject line.", }, { id: "lb-meeting-summary", title: "Summarize a meeting or call", function: "General", stage: "Lightbulb", tags: ["summary", "notes", "action items"], description: "Turn raw notes into takeaways and owner-assigned action items.", template: "Here are my meeting notes/transcript:\n\n---\n{{meeting_notes}}\n---\n\nSummarize into: 1) key decisions, 2) action items with owners and due dates, 3) risks/dependencies, 4) 3-sentence executive summary.", }, { id: "lb-compare-reports", title: "Compare two reports", function: "General", stage: "Lightbulb", tags: ["analysis", "variance", "reports"], description: "Paste two datasets or summaries—get differences, trends, issues.", template: "Compare the following two reports and highlight: major differences, emerging trends, anomalies, and 3 recommendations.\n\nReport A:\n---\n{{report_a}}\n---\nReport B:\n---\n{{report_b}}\n---\n\nReturn a concise table of key deltas and a short narrative.", }, { id: "lb-idea-to-plan", title: "Turn an idea into a plan", function: "General", stage: "Lightbulb", tags: ["planning", "execution"], description: "Convert a rough idea into goals, steps, timeline, risks.", template: "Here’s my rough idea:\n\n{{idea_text}}\n\nTurn this into a one-page plan with: objective, scope, success metrics, milestones timeline (with owners), risks/mitigations, and next 3 actions for this week.", }, // Operations { id: "ops-wms-entry", title: "Prepare order entry for WMS", function: "Operations", stage: "Everyday", tags: ["extensiv", "orders", "warehouse"], description: "Review paperwork and output clean fields for WMS import. Follows SKU composition and quantity rules.", template: "You are an experienced CSR entering Outbound Orders into Extensiv WMS. Review the document and produce a structured table for import.\n\nRules:\n- SKU = STYLE + '-' + COLOR + '-' + SCALE (e.g., 23159-DBAB-LTIND-07).\n- If Quantity is not an integer, assume 0 and exclude the line.\n- Map fields: Customer Order Number → Reference Number; Purchase Order Number → PO Number; Ship Carrier → Ship Via; SKU per logic; Quantity → Scale column; Lot Code/# → Load Number; Batch ID/Number → Notes.\n\nDocument:\n---\n{{order_paperwork}}\n---\n\nReturn a CSV with the mapped columns only.", }, { id: "ops-inventory-snapshot", title: "Inventory snapshot & alerts", function: "Operations", stage: "Everyday", tags: ["inventory", "exceptions", "warehouse"], description: "Highlight shortages, overstocks, and at-risk SKUs from a report.", template: "From this inventory export, provide: 1) current on-hand and available by SKU, 2) under min/over max flags, 3) the top 10 at-risk SKUs.\n\nInventory Export:\n---\n{{inventory_csv_or_text}}\n---\n\nReturn a markdown table (SKU, On-Hand, Available, Min, Max, Flag, Note).", }, { id: "ops-sop", title: "Create or improve an SOP", function: "Operations", stage: "Everyday", tags: ["SOP", "training", "quality"], description: "Step-by-step SOP with a checklist and training notes.", template: "Create a concise SOP for process: {{process_name}}.\nInclude: Purpose, Preconditions, Step-by-step procedure (numbered), Quality checks, Safety notes, Common errors, and a one-page checklist version for training.\nIf an existing SOP is pasted below, first identify 5 improvements.\n\nExisting SOP (optional):\n{{existing_sop}}", }, // Customer Service { id: "cs-respond", title: "Customer inquiry response", function: "Customer Service", stage: "Everyday", tags: ["customer", "email", "empathy"], description: "Draft a helpful, reassuring reply with next steps.", template: "Customer message:\n---\n{{customer_message}}\n---\nDraft a helpful, empathetic response that answers questions, sets expectations, and lists next steps with timelines. Include a friendly subject line.", }, { id: "cs-proactive-update", title: "Proactive delay update", function: "Customer Service", stage: "Everyday", tags: ["delay", "communication"], description: "Explain a delay with clarity and options.", template: "We have a delay affecting shipment(s): {{shipment_refs}}. Root cause: {{root_cause}}. New ETA: {{new_eta}}. Draft a proactive customer update with options, escalation path, and reassurance. Keep it concise and professional.", }, // Finance { id: "fin-invoice-recon", title: "Invoice reconciliation by shipment", function: "Finance", stage: "Everyday", tags: ["AP", "invoices", "audit"], description: "Compare carrier invoices to AP ledger and flag issues.", template: "Compare these carrier invoices to the AP ledger. Break out discrepancies by shipment number and classify them (rate, fuel, accessorial, duplicate, missing).\n\nInvoices:\n{{invoices_text_or_csv}}\n\nAP Ledger:\n{{ap_ledger_text_or_csv}}\n\nReturn a table with: Shipment #, Issue Type, Amount, Notes, Action.", }, { id: "fin-qb-import", title: "QuickBooks import builder", function: "Finance", stage: "Everyday", tags: ["QuickBooks", "billing"], description: "Create a QB bills/import file with markup.", template: "From this invoice data, generate a QuickBooks bills import (CSV) for Vendor {{vendor_name}}. Apply {{markup_percent}}% markup as a service fee line.\n\nInvoice Data:\n{{invoice_data}}\n\nReturn CSV columns: Vendor, Bill Date, Due Date, Reference No, Item/Account, Description, Amount, Customer/Project (optional).", }, // Sales { id: "sales-proposal", title: "Draft a customer proposal", function: "Sales", stage: "Everyday", tags: ["proposal", "sales"], description: "Problem → Solution → Benefits → Next Steps.", template: "Draft a short proposal for {{customer_name}} (industry: {{industry}}). Include: 1) problem statement, 2) our solution, 3) 5 quantified benefits, 4) timeline & next steps, 5) assumptions. Tone: consultative, concise.", }, { id: "sales-linkedin", title: "LinkedIn outreach message", function: "Sales", stage: "Everyday", tags: ["linkedin", "outreach"], description: "Polite, relevant, non-salesy connect message.", template: "Write a 280-character LinkedIn message to a {{title}} at {{company}} about {{relevant_trigger}}. Goal: start a conversation, not pitch. Offer a useful resource and ask a low-friction question.", }, // HR { id: "hr-job-post", title: "Draft a job posting", function: "HR", stage: "Everyday", tags: ["hiring", "job post"], description: "Clear role, responsibilities, must-haves, why join.", template: "Create a job posting for role: {{role_title}}. Include: mission, responsibilities, must-have skills, nice-to-haves, success metrics (first 90 days), and a short blurb on why join us (culture + impact).", }, { id: "hr-feedback", title: "Rewrite performance feedback", function: "HR", stage: "Everyday", tags: ["coaching", "communication"], description: "Maintain standards while staying supportive.", template: "Rewrite this feedback to be clear, supportive, and actionable, while maintaining firm standards.\n\nFeedback:\n{{feedback_text}}", }, // Strategy { id: "strat-plan-review", title: "Strategic plan review", function: "General", stage: "Strategy", tags: ["strategy", "review", "coach"], description: "Identify strengths, risks, and improvements.", template: "Act as an executive coach. Review this strategic plan and identify strengths, weaknesses, key risks, and the top improvements.\n\nPlan:\n{{plan_text_or_link}}\n\nReturn: 10-bullet executive summary and a 90-day focus list.", }, { id: "strat-risk", title: "Risk assessment & second-order effects", function: "General", stage: "Strategy", tags: ["risk", "decision"], description: "Surface risks and mitigations before decisions.", template: "Situation:\n{{situation}}\n\nList potential risks and second-order effects. Rate likelihood and impact (Low/Med/High), propose mitigations, and list trigger signals to monitor.", }, // Advanced { id: "adv-roleplay-board", title: "Role-play: board member Q&A", function: "General", stage: "Advanced", tags: ["role-play", "board", "challenge"], description: "Stress-test your plan with tough questions.", template: "Act as a growth-minded board member reviewing our plan. Ask the 7 toughest questions you’d raise, then evaluate my drafted answers (I will reply) and score clarity, evidence, and feasibility.", }, { id: "adv-negotiate", title: "Role-play: vendor negotiation", function: "General", stage: "Advanced", tags: ["negotiation", "practice"], description: "Practice countering pushback and improve offers.", template: "Role-play a vendor negotiating price and terms for {{item_or_service}}. Push back realistically. After the exercise, critique my approach and suggest 3 improvements.", }, ]; // ---------- Helpers ---------- const FUNCTIONS = ["All", "Operations", "Customer Service", "Finance", "Sales", "HR", "General"] as const; const STAGES = ["All", "Lightbulb", "Everyday", "Strategy", "Advanced"] as const; const SAVED_KEY = "briefli_saved_prompts_v1"; type FnFilter = typeof FUNCTIONS[number]; type StageFilter = typeof STAGES[number]; const escapeForUrl = (s: string) => encodeURIComponent(s); const CHATGPT_NEW_CHAT = "https://chat.openai.com/"; // new chat; user pastes or uses prefill param below const composePrefillUrl = (text: string) => `${CHATGPT_NEW_CHAT}?temporary-chat=true&input=${escapeForUrl(text)}`; export function extractVariables(template: string): string[] { const vars = new Set(); const re = /\{\{\s*([a-zA-Z0-9_\-]+)\s*\}\}/g; let m; while ((m = re.exec(template))) vars.add(m[1]); return Array.from(vars); } export function applyTemplate(template: string, values: Record): string { return template.replace(/\{\{\s*([a-zA-Z0-9_\-]+)\s*\}\}/g, (_: unknown, k: string) => (values[k] ?? `{{${k}}}`)); } // ---------- Document reading helpers (TXT/PDF/DOCX) ---------- function readFileAsArrayBuffer(file: File): Promise { return new Promise((res, rej) => { const fr = new FileReader(); fr.onload = () => res(fr.result as ArrayBuffer); fr.onerror = rej; fr.readAsArrayBuffer(file); }); } function readFileAsText(file: File): Promise { return new Promise((res, rej) => { const fr = new FileReader(); fr.onload = () => res(fr.result as string); fr.onerror = rej; fr.readAsText(file); }); } async function pdfToText(file: File): Promise { const buf = await readFileAsArrayBuffer(file); // IMPORTANT: operate without a worker in sandbox const pdf = await pdfjsLib.getDocument({ data: buf, disableWorker: true }).promise; let out: string[] = []; for (let p = 1; p <= pdf.numPages; p++) { const page = await pdf.getPage(p); const content = await page.getTextContent(); let line = ""; for (const item of content.items as any[]) { const str = (item.str ?? "").toString(); line += (line ? " " : "") + str; if (item.hasEOL) { out.push(line.trim()); line = ""; } } if (line.trim()) out.push(line.trim()); } return out.join("\n"); } async function docxToText(file: File): Promise { const arrayBuffer = await readFileAsArrayBuffer(file); const result = await mammoth.extractRawText({ arrayBuffer }); return (result.value || "").trim(); } async function fileToText(file: File): Promise { const name = file.name.toLowerCase(); if (name.endsWith(".pdf")) return pdfToText(file); if (name.endsWith(".docx")) return docxToText(file); return readFileAsText(file); // .txt, .csv etc. } // ---------- Quick Export rules ---------- export type PdfRules = { delimiter?: string; columns?: string[]; skipHeaderRows?: number; regex?: string; }; export type RuleSet = { select?: string[]; rename?: Record; filter?: { column: string; op: "=="|"!="|">"|">="|"<"|"<="|"contains"; value: any }[]; add?: { column: string; formula: string }[]; pdf?: PdfRules; }; async function parsePdfToLines(file: File): Promise { const buf = await readFileAsArrayBuffer(file); const pdf = await pdfjsLib.getDocument({ data: buf, disableWorker: true }).promise; const lines: string[] = []; for (let p = 1; p <= pdf.numPages; p++) { const page = await pdf.getPage(p); const content = await page.getTextContent(); let line = ""; for (const item of content.items as any[]) { const str = (item.str ?? "").toString(); line += (line ? " " : "") + str; if (item.hasEOL) { lines.push(line.trim()); line = ""; } } if (line.trim()) lines.push(line.trim()); } return lines.filter(Boolean); } function linesToRows(lines: string[], pdfRules?: PdfRules): Record[] { const rules = pdfRules ?? {}; const out: Record[] = []; const start = Math.max(0, rules.skipHeaderRows ?? 0); const useRegex = rules.regex ? new RegExp(rules.regex) : null; const splitter = (s: string) => { if (rules.delimiter) return s.split(rules.delimiter); return s.trim().split(/\s{2,}|\t|\|/); }; let columns = rules.columns?.slice(); if (!columns || columns.length === 0) { const trial = splitter(lines[start] || ""); columns = trial.map((_, i) => `c${i+1}`); } for (let i = start; i < lines.length; i++) { const L = lines[i]; if (!L) continue; if (useRegex) { const m = L.match(useRegex); if (m && (m.groups || Object.keys(m).length)) { out.push({ ...(m.groups || {}) }); } continue; } const parts = splitter(L); const row: Record = {}; for (let c = 0; c < columns.length; c++) row[columns[c]] = parts[c] ?? ""; out.push(row); } return out; } function parseDataFileBasic(file: File): Promise[]> { const name = file.name.toLowerCase(); if (name.endsWith(".xlsx") || name.endsWith(".xls")) { return readFileAsArrayBuffer(file).then((buf) => { const wb = XLSX.read(buf, { type: "array" }); const ws = wb.Sheets[wb.SheetNames[0]]; return XLSX.utils.sheet_to_json(ws, { defval: "" }) as Record[]; }); } if (name.endsWith(".pdf")) { return parsePdfToLines(file).then((ls) => ls.map((t) => ({ text: t }))); } return readFileAsText(file).then((text) => { const rows = text.split(/\r?\n/).filter(Boolean).map((r) => r.split(",")); if (rows.length === 0) return []; const headers = rows[0]; return rows.slice(1).map((r) => { const o: Record = {}; headers.forEach((h, i) => (o[h] = r[i] ?? "")); return o; }); }); } function deriveColumnsFromTemplate(file: File): Promise { return parseDataFileBasic(file).then((rows) => (rows[0] ? Object.keys(rows[0]) : [])); } function applyRules(rows: Record[], rules: RuleSet): Record[] { let out = [...rows]; if (rules.filter?.length) { out = out.filter((row) => { return rules.filter!.every((f) => { const v = row[f.column]; const t = typeof v === "string" ? v.toLowerCase() : v; const cmp = typeof f.value === "string" ? (f.value as string).toLowerCase() : f.value; switch (f.op) { case "==": return v == f.value; case "!=": return v != f.value; case ">": return Number(v) > Number(f.value); case ">=": return Number(v) >= Number(f.value); case "<": return Number(v) < Number(f.value); case "<=": return Number(v) <= Number(f.value); case "contains": return typeof t === "string" && typeof cmp === "string" && t.includes(cmp); default: return true; } }); }); } if (rules.add?.length) { out = out.map((row, index) => { const next = { ...row } as Record; for (const add of rules.add!) { try { // eslint-disable-next-line no-new-func const fn = new Function("row", "index", add.formula) as (row: any, index: number) => any; next[add.column] = fn(row, index); } catch (e) { next[add.column] = ""; } } return next; }); } if (rules.rename) { out = out.map((row) => { const next: Record = {}; Object.keys(row).forEach((k) => { const nk = rules.rename![k] ?? k; next[nk] = row[k]; }); return next; }); } if (rules.select?.length) { out = out.map((row) => { const next: Record = {}; rules.select!.forEach((k) => (next[k] = row[k] ?? "")); return next; }); } return out; } function toWorkbook(rows: Record[], templateHeaders?: string[]) { let headers: string[]; if (templateHeaders?.length) { headers = templateHeaders; } else if (rows[0]) { headers = Object.keys(rows[0]); } else { headers = []; } const data = [headers, ...rows.map((r) => headers.map((h) => r[h] ?? ""))]; const ws = XLSX.utils.aoa_to_sheet(data); const wb = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, "Export"); return wb; } // ---------- UI Components ---------- const Chip: React.FC<{ label: string; active?: boolean; onClick?: () => void }>=({ label, active, onClick }) => ( ); const SectionHeader: React.FC<{ title: string; subtitle?: string }>=({ title, subtitle }) => (

{title}

{subtitle &&

{subtitle}

}
); // ---------- Self Tests (helpers) ---------- function runSelfTests() { try { const t1 = "Hello {{name}} your {{item-1}} is ready"; const vars1 = extractVariables(t1).sort(); console.assert(JSON.stringify(vars1) === JSON.stringify(["item-1", "name"].sort()), "extractVariables captures hyphenated vars"); const t2 = "No variables here"; const vars2 = extractVariables(t2); console.assert(vars2.length === 0, "extractVariables returns empty if none"); const out1 = applyTemplate("Hi {{name}}", { name: "Mike" }); console.assert(out1 === "Hi Mike", "applyTemplate replaces tokens"); const out2 = applyTemplate("Hi {{name}} {{missing}}", { name: "Sam" }); console.assert(out2.includes("{{missing}}"), "applyTemplate preserves unknown tokens"); // Quick Export rules tests (non-PDF) const rows = [ { A:1, B:2, Text:"abc" }, { A:3, B:4, Text:"def" } ]; const rules = { select:["A","Sum"], add:[{column:"Sum", formula:"return (Number(row.A)||0)+(Number(row.B)||0);"}], filter:[{column:"Text", op:"contains", value:"a"}] } as RuleSet; const transformed = applyRules(rows, rules); console.assert(transformed.length === 1 && transformed[0].Sum === 3, "applyRules add/filter/select works"); // Lines → Rows tests (PDF-style parsing logic) const pdfLines = ["2024-01-01 ABC123 10.50", "2024-01-02 XYZ555 0.00"]; const parsedPdf = linesToRows(pdfLines, { delimiter: undefined, columns:["Date","BOL","Total"] }); console.assert(parsedPdf[0].Date === "2024-01-01" && parsedPdf[0].Total === "10.50", "linesToRows default splitter works"); // Variable fill workflow (doc upload simulation) const mockText = "Line1\nLine2"; const filled = applyTemplate("Doc: {{existing_sop}}", { existing_sop: mockText }); console.assert(filled.includes("Line2"), "uploaded text flows into variables"); // New: ensure we didn't set workerSrc improperly try { // @ts-ignore const hasWorkerSrc = pdfjsLib?.GlobalWorkerOptions?.workerSrc; console.assert(hasWorkerSrc === undefined || typeof hasWorkerSrc === "string", "workerSrc should be undefined or string (not object/other)"); } catch {} if (typeof console !== "undefined") console.log("✅ Self-tests passed"); } catch (err) { if (typeof console !== "undefined") console.error("❌ Self-tests failed", err); } } // ---------- Main Component ---------- export default function PromptLibrary() { const [tab, setTab] = useState<"library"|"export"|"saved">("library"); // Library state const [q, setQ] = useState(""); const [fnFilter, setFnFilter] = useState("All"); const [stageFilter, setStageFilter] = useState("All"); const [selected, setSelected] = useState(null); const [values, setValues] = useState>({}); // Saved prompts state const [saved, setSaved] = useState([]); const [savedQ, setSavedQ] = useState(""); // Export state const [dataFile, setDataFile] = useState(null); const [templateFile, setTemplateFile] = useState(null); const [templateHeaders, setTemplateHeaders] = useState(); const [rulesText, setRulesText] = useState(`{\n \"select\": [],\n \"rename\": {},\n \"filter\": [],\n \"add\": [],\n \"pdf\": {\n \"delimiter\": \"\",\n \"columns\": [],\n \"skipHeaderRows\": 0,\n \"regex\": \"\"\n }\n}`); const [dataPreview, setDataPreview] = useState[]>([]); const [exportName, setExportName] = useState("export.xlsx"); // run tests on mount in non-production environments useEffect(() => { const dev = typeof process !== "undefined" && process?.env?.NODE_ENV !== "production"; if (dev) runSelfTests(); try { const raw = localStorage.getItem(SAVED_KEY); if (raw) setSaved(JSON.parse(raw)); } catch {} }, []); useEffect(() => { if (!selected) return; const vars = extractVariables(selected.template); const init: Record = {}; vars.forEach(v => (init[v] = values[v] ?? "")); setValues(init); // eslint-disable-next-line react-hooks/exhaustive-deps }, [selected?.id]); const filtered = useMemo(() => { const needle = q.trim().toLowerCase(); return PROMPTS.filter(p => { const matchesText = !needle || [p.title, p.description ?? "", p.template, ...p.tags].join("\n").toLowerCase().includes(needle); const matchesFn = fnFilter === "All" || p.function === fnFilter; const matchesStage = stageFilter === "All" || p.stage === stageFilter; return matchesText && matchesFn && matchesStage; }); }, [q, fnFilter, stageFilter]); // ----- Saved prompts helpers ----- const persistSaved = (next: SavedPrompt[]) => { setSaved(next); try { localStorage.setItem(SAVED_KEY, JSON.stringify(next)); } catch {} }; const onSavePrompt = () => { if (!selected) return alert("Open a prompt first"); const text = applyTemplate(selected.template, values); const title = prompt("Name this saved prompt", selected.title) || selected.title; const entry: SavedPrompt = { id: `${Date.now()}-${Math.random().toString(36).slice(2,8)}`, promptId: selected.id, title, filledText: text, savedAt: Date.now() }; persistSaved([entry, ...saved]); alert("Saved ✅"); }; const onDeleteSaved = (id: string) => { persistSaved(saved.filter(s => s.id !== id)); }; const filteredSaved = useMemo(() => { const needle = savedQ.trim().toLowerCase(); return saved.filter(s => !needle || s.title.toLowerCase().includes(needle) || s.filledText.toLowerCase().includes(needle)); }, [saved, savedQ]); // ----- Quick Export handlers ----- const onLoadData = async () => { if (!dataFile) return alert("Upload a data file first"); const rows = await parseDataFileBasic(dataFile); setDataPreview(rows.slice(0, 100)); }; const onLoadTemplate = async () => { if (!templateFile) return alert("Upload a template file first"); const headers = await deriveColumnsFromTemplate(templateFile); if (!headers.length) return alert("No headers detected in template file"); setTemplateHeaders(headers); alert(`Template headers loaded: ${headers.join(", ")}`); }; const onExportExcel = async () => { if (!dataFile) return alert("Upload a data file first"); let rules: RuleSet = {}; try { rules = JSON.parse(rulesText); } catch { return alert("Rules must be valid JSON"); } const name = dataFile.name.toLowerCase(); let rows: Record[] = []; if (name.endsWith(".pdf")) { const lines = await parsePdfToLines(dataFile); rows = linesToRows(lines, rules.pdf); } else { rows = await parseDataFileBasic(dataFile); } rows = applyRules(rows, rules); const wb = toWorkbook(rows, templateHeaders); XLSX.writeFile(wb, exportName || "export.xlsx"); }; const onCopy = async () => { if (!selected) return; const text = applyTemplate(selected.template, values); try { await navigator.clipboard.writeText(text); alert("Prompt copied to clipboard ✨"); } catch { const ta = document.createElement("textarea"); ta.value = text; document.body.appendChild(ta); ta.select(); document.execCommand("copy"); document.body.removeChild(ta); alert("Prompt copied to clipboard ✨"); } }; const onStart = () => { if (!selected) return; const text = applyTemplate(selected.template, values); const url = composePrefillUrl(text); window.open(url, "_blank"); }; const exportJson = () => { const data = JSON.stringify(filtered, null, 2); const blob = new Blob([data], { type: "application/json" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "prompt-library.json"; a.click(); URL.revokeObjectURL(url); }; // ----- Document upload into variable ----- const handleVarFile = async (vKey: string, file?: File | null) => { if (!file) return; try { const text = await fileToText(file); setValues(prev => ({ ...prev, [vKey]: (prev[vKey] ? (prev[vKey] + "\n\n" + text) : text) })); } catch (e) { alert("Could not read file. Please ensure it is PDF, DOCX, or TXT."); } }; return (
{/* Header */}

AI Prompt Library

Search • Filter • Customize • Start
{/* Tabs */}
{/* ----- LIBRARY TAB ----- */} {tab === "library" && ( <> {/* Search & Filters */}
setQ(e.target.value)} />
{FUNCTIONS.map(f => ( setFnFilter(f)} /> ))}
{STAGES.map(s => ( setStageFilter(s)} /> ))}
{/* Results */}
{filtered.map(p => (
setSelected(p)}> {p.title}
{p.function} {p.stage}

{p.description}

{p.tags.map(t => #{t})}
))}
{/* Drawer / Modal substitute */} {selected && (
setSelected(null)}>
e.stopPropagation()}>

{selected.title}

{selected.function} • {selected.stage}

{selected.description &&

{selected.description}

} {/* Variables */}
{extractVariables(selected.template).map(v => (