Spaces:
Running
Running
Download src/components/InteractiveDocumentCard.tsx from Aura-Studio/AI: direct link, hf CLI and curl.
- Browser
- Download file 8.23 kB
-
https://huggingface.co/spaces/Aura-Studio/AI/resolve/main/src/components/InteractiveDocumentCard.tsx
- Command line
-
hf download hf://spaces/Aura-Studio/AI/src/components/InteractiveDocumentCard.tsx
-
curl -L -o InteractiveDocumentCard.tsx https://huggingface.co/spaces/Aura-Studio/AI/resolve/main/src/components/InteractiveDocumentCard.tsx
8.23 kB
| import React, { useState } from "react"; | |
| import { FileText, FileSpreadsheet, Presentation, Download, Sparkles, Check, AlertCircle, Lock } from "lucide-react"; | |
| import type { DocType } from "../utils/documentGenerator"; | |
| interface InteractiveDocumentCardProps { | |
| docData: { | |
| type: DocType; | |
| title: string; | |
| subtitle?: string; | |
| headers?: string[]; | |
| rows?: Array<Array<string | number | boolean>>; | |
| sections?: Array<{ heading?: string; body: string | string[] }>; | |
| slides?: Array<{ title: string; bullets?: string[]; content?: string; keyTakeaway?: string }>; | |
| }; | |
| currentUserTier?: "guest" | "free" | "pro" | "tiniagents"; | |
| onUpgradeClick?: () => void; | |
| } | |
| export const InteractiveDocumentCard: React.FC<InteractiveDocumentCardProps> = ({ | |
| docData, | |
| currentUserTier = "free", | |
| onUpgradeClick | |
| }) => { | |
| const [isGenerating, setIsGenerating] = useState(false); | |
| const [downloadSuccess, setDownloadSuccess] = useState(false); | |
| const [errorMsg, setErrorMsg] = useState<string | null>(null); | |
| const docType = docData.type || "docx"; | |
| const title = docData.title || "Generated Document"; | |
| const slidesCount = docData.slides?.length || 1; | |
| // Document creation is currently available to every tier. | |
| const perm: { allowed: boolean; maxSlidesAllowed: number; reason?: string } = { | |
| allowed: true, | |
| maxSlidesAllowed: 20 | |
| }; | |
| const getDocIcon = () => { | |
| switch (docType) { | |
| case "xlsx": | |
| return <FileSpreadsheet className="h-6 w-6 text-emerald-500" />; | |
| case "pdf": | |
| return <FileText className="h-6 w-6 text-rose-500" />; | |
| case "pptx": | |
| return <Presentation className="h-6 w-6 text-amber-500" />; | |
| case "docx": | |
| default: | |
| return <FileText className="h-6 w-6 text-indigo-500" />; | |
| } | |
| }; | |
| const getDocBadge = () => { | |
| switch (docType) { | |
| case "xlsx": | |
| return { label: "Excel Spreadsheet (.xlsx)", color: "bg-emerald-500/10 text-emerald-600 dark:text-emerald-400 border-emerald-500/30" }; | |
| case "pdf": | |
| return { label: "PDF Document (.pdf)", color: "bg-rose-500/10 text-rose-600 dark:text-rose-400 border-rose-500/30" }; | |
| case "pptx": | |
| return { label: `PowerPoint (${slidesCount} Slides)`, color: "bg-amber-500/10 text-amber-600 dark:text-amber-400 border-amber-500/30" }; | |
| case "docx": | |
| default: | |
| return { label: "Word Document (.docx)", color: "bg-indigo-500/10 text-indigo-600 dark:text-indigo-400 border-indigo-500/30" }; | |
| } | |
| }; | |
| const badge = getDocBadge(); | |
| const handleDownload = async () => { | |
| setErrorMsg(null); | |
| if (!perm.allowed) { | |
| setErrorMsg(perm.reason || "This document format is restricted for your active subscription tier."); | |
| return; | |
| } | |
| try { | |
| setIsGenerating(true); | |
| const { | |
| createWordDocument, | |
| createExcelSpreadsheet, | |
| createPdfDocument, | |
| createPptxPresentation, | |
| triggerFileDownload | |
| } = await import("../utils/documentGenerator"); | |
| let blob: Blob; | |
| let defaultFileName = `${title.toLowerCase().replace(/[^a-z0-9]/g, "_")}.${docType}`; | |
| if (docType === "docx") { | |
| blob = await createWordDocument({ | |
| title, | |
| subtitle: docData.subtitle, | |
| sections: docData.sections || [{ heading: "Summary", body: title }] | |
| }); | |
| } else if (docType === "xlsx") { | |
| blob = createExcelSpreadsheet({ | |
| fileName: title, | |
| headers: docData.headers || ["Item", "Description", "Value"], | |
| rows: docData.rows || [] | |
| }); | |
| } else if (docType === "pdf") { | |
| blob = createPdfDocument({ | |
| title, | |
| subtitle: docData.subtitle, | |
| content: (docData.sections || []).map((s) => ({ | |
| heading: s.heading, | |
| text: Array.isArray(s.body) ? s.body.join("\n\n") : s.body | |
| })) | |
| }); | |
| } else if (docType === "pptx") { | |
| const maxSlides = perm.maxSlidesAllowed || 10; | |
| blob = await createPptxPresentation( | |
| { | |
| title, | |
| subtitle: docData.subtitle, | |
| slides: docData.slides || [] | |
| }, | |
| maxSlides | |
| ); | |
| } else { | |
| throw new Error("Unsupported document type"); | |
| } | |
| triggerFileDownload(blob, defaultFileName); | |
| setDownloadSuccess(true); | |
| setTimeout(() => setDownloadSuccess(false), 4000); | |
| // Track document generation usage in background | |
| try { | |
| const currentUsername = (window as any)?.__AURA_CURRENT_USER__?.username || localStorage.getItem("aura_user_name") || ""; | |
| fetch("/api/subscriptions/track-usage", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| username: currentUsername, | |
| type: "documents", | |
| count: 1 | |
| }) | |
| }).catch(() => {}); | |
| } catch (e) {} | |
| } catch (err: any) { | |
| console.error("Failed to generate document:", err); | |
| setErrorMsg(err.message || "An error occurred while generating the document."); | |
| } finally { | |
| setIsGenerating(false); | |
| } | |
| }; | |
| return ( | |
| <div className="my-4 rounded-2xl border border-slate-200 dark:border-slate-800 bg-gradient-to-br from-white via-slate-50/50 to-indigo-50/20 dark:from-slate-900 dark:via-slate-900/80 dark:to-slate-950 p-4 shadow-md transition-all hover:shadow-lg"> | |
| <div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3"> | |
| <div className="flex items-center gap-3 min-w-0"> | |
| <div className="h-11 w-11 rounded-xl bg-slate-100 dark:bg-slate-800 border border-slate-200/80 dark:border-slate-700/80 flex items-center justify-center shrink-0 shadow-xs"> | |
| {getDocIcon()} | |
| </div> | |
| <div className="min-w-0"> | |
| <div className="flex items-center gap-2 flex-wrap"> | |
| <span className={`text-[10px] font-bold uppercase tracking-wider px-2 py-0.5 rounded-md border ${badge.color}`}> | |
| {badge.label} | |
| </span> | |
| <span className="text-[10px] font-bold text-emerald-600 dark:text-emerald-400 bg-emerald-500/10 px-1.5 py-0.5 rounded border border-emerald-500/20"> | |
| 100% FREE | |
| </span> | |
| </div> | |
| <h4 className="text-sm font-bold text-slate-900 dark:text-white truncate mt-1"> | |
| {title} | |
| </h4> | |
| {docData.subtitle && ( | |
| <p className="text-xs text-slate-500 dark:text-slate-400 truncate"> | |
| {docData.subtitle} | |
| </p> | |
| )} | |
| </div> | |
| </div> | |
| {/* Action Button */} | |
| <div className="shrink-0 w-full sm:w-auto"> | |
| <button | |
| onClick={handleDownload} | |
| disabled={isGenerating} | |
| className={`w-full sm:w-auto h-10 px-4 rounded-xl font-bold text-xs flex items-center justify-center gap-2 transition-all cursor-pointer ${ | |
| downloadSuccess | |
| ? "bg-emerald-600 text-white shadow-md shadow-emerald-600/20" | |
| : "bg-indigo-600 hover:bg-indigo-500 text-white shadow-md shadow-indigo-600/20 active:scale-95" | |
| }`} | |
| > | |
| {isGenerating ? ( | |
| <> | |
| <Sparkles className="h-4 w-4 animate-spin text-amber-300" /> | |
| <span>Compiling Document...</span> | |
| </> | |
| ) : downloadSuccess ? ( | |
| <> | |
| <Check className="h-4 w-4 text-emerald-200" /> | |
| <span>Downloaded!</span> | |
| </> | |
| ) : ( | |
| <> | |
| <Download className="h-4 w-4" /> | |
| <span>Download {docType.toUpperCase()}</span> | |
| </> | |
| )} | |
| </button> | |
| </div> | |
| </div> | |
| {/* Error / Restriction Warning */} | |
| {errorMsg && ( | |
| <div className="mt-3 p-2.5 rounded-xl bg-amber-500/10 border border-amber-500/30 text-amber-700 dark:text-amber-300 text-xs flex items-start gap-2"> | |
| <AlertCircle className="h-4 w-4 text-amber-500 shrink-0 mt-0.5" /> | |
| <div className="flex-1"> | |
| <span>{errorMsg}</span> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| }; | |