Spaces:
Build error
Build error
Upload pages/index.js with huggingface_hub
Browse files- pages/index.js +140 -0
pages/index.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState, useEffect } from 'react';
|
| 2 |
+
import Header from '../components/Header';
|
| 3 |
+
import ThreadList from '../components/ThreadList';
|
| 4 |
+
import ExportControls from '../components/ExportControls';
|
| 5 |
+
import { Database, FileJson, FileText, RefreshCw, CheckCircle, AlertCircle } from 'lucide-react';
|
| 6 |
+
|
| 7 |
+
// Mock data generator to simulate Perplexity threads
|
| 8 |
+
const generateMockThreads = (count) => {
|
| 9 |
+
return Array.from({ length: count }).map((_, i) => ({
|
| 10 |
+
id: `thread-${Date.now()}-${i}`,
|
| 11 |
+
title: `Research Topic #${i + 1}: Artificial Intelligence in ${['Medicine', 'Finance', 'Education', 'Climate'][i % 4]}`,
|
| 12 |
+
date: new Date(Date.now() - i * 86400000).toLocaleDateString(),
|
| 13 |
+
query: `How does AI impact ${['healthcare', 'banking', 'schools', 'environment'][i % 4]}?`,
|
| 14 |
+
citations: Math.floor(Math.random() * 15) + 2,
|
| 15 |
+
status: 'completed'
|
| 16 |
+
}));
|
| 17 |
+
};
|
| 18 |
+
|
| 19 |
+
export default function Home() {
|
| 20 |
+
const [threads, setThreads] = useState([]);
|
| 21 |
+
const [loading, setLoading] = useState(false);
|
| 22 |
+
const [exporting, setExporting] = useState(false);
|
| 23 |
+
const [exportProgress, setExportProgress] = useState(0);
|
| 24 |
+
const [selectedThreads, setSelectedThreads] = useState([]);
|
| 25 |
+
const [notification, setNotification] = useState(null);
|
| 26 |
+
|
| 27 |
+
useEffect(() => {
|
| 28 |
+
loadThreads();
|
| 29 |
+
}, []);
|
| 30 |
+
|
| 31 |
+
const loadThreads = async () => {
|
| 32 |
+
setLoading(true);
|
| 33 |
+
// Simulate API delay
|
| 34 |
+
setTimeout(() => {
|
| 35 |
+
setThreads(generateMockThreads(20));
|
| 36 |
+
setLoading(false);
|
| 37 |
+
}, 1000);
|
| 38 |
+
};
|
| 39 |
+
|
| 40 |
+
const toggleSelection = (id) => {
|
| 41 |
+
setSelectedThreads(prev =>
|
| 42 |
+
prev.includes(id) ? prev.filter(t => t !== id) : [...prev, id]
|
| 43 |
+
);
|
| 44 |
+
};
|
| 45 |
+
|
| 46 |
+
const handleExport = async (format) => {
|
| 47 |
+
if (selectedThreads.length === 0) {
|
| 48 |
+
setNotification({ type: 'error', message: 'Please select at least one thread.' });
|
| 49 |
+
return;
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
setExporting(true);
|
| 53 |
+
setExportProgress(0);
|
| 54 |
+
|
| 55 |
+
// Simulate batch processing
|
| 56 |
+
const totalSteps = 5;
|
| 57 |
+
for (let i = 1; i <= totalSteps; i++) {
|
| 58 |
+
await new Promise(r => setTimeout(r, 400));
|
| 59 |
+
setExportProgress((i / totalSteps) * 100);
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
// Trigger download
|
| 63 |
+
const dataToExport = threads.filter(t => selectedThreads.includes(t.id));
|
| 64 |
+
const content = format === 'json'
|
| 65 |
+
? JSON.stringify(dataToExport, null, 2)
|
| 66 |
+
: dataToExport.map(t => `${t.id},${t.title},${t.query}`).join('\n');
|
| 67 |
+
|
| 68 |
+
const blob = new Blob([content], { type: format === 'json' ? 'application/json' : 'text/csv' });
|
| 69 |
+
const url = window.URL.createObjectURL(blob);
|
| 70 |
+
const a = document.createElement('a');
|
| 71 |
+
a.href = url;
|
| 72 |
+
a.download = `perplexity-batch-${Date.now()}.${format}`;
|
| 73 |
+
a.click();
|
| 74 |
+
window.URL.removeObjectURL(url);
|
| 75 |
+
|
| 76 |
+
setExporting(false);
|
| 77 |
+
setNotification({ type: 'success', message: `Successfully exported ${selectedThreads.length} threads as ${format.toUpperCase()}.` });
|
| 78 |
+
setSelectedThreads([]);
|
| 79 |
+
};
|
| 80 |
+
|
| 81 |
+
return (
|
| 82 |
+
<div className="min-h-screen flex flex-col font-sans">
|
| 83 |
+
<Header />
|
| 84 |
+
|
| 85 |
+
<main className="flex-1 p-6 max-w-7xl mx-auto w-full">
|
| 86 |
+
{/* Header Section */}
|
| 87 |
+
<div className="mb-8 flex flex-col md:flex-row md:items-center justify-between gap-4">
|
| 88 |
+
<div>
|
| 89 |
+
<h1 className="text-3xl font-bold text-white mb-2">Thread Exporter</h1>
|
| 90 |
+
<p className="text-gray-400">Manage and export your Perplexity research threads in batches.</p>
|
| 91 |
+
</div>
|
| 92 |
+
<div className="flex gap-3">
|
| 93 |
+
<button
|
| 94 |
+
onClick={loadThreads}
|
| 95 |
+
disabled={loading}
|
| 96 |
+
className="flex items-center gap-2 px-4 py-2 bg-slate-800 hover:bg-slate-700 text-white rounded-lg transition-colors disabled:opacity-50"
|
| 97 |
+
>
|
| 98 |
+
<RefreshCw size={18} className={loading ? 'animate-spin' : ''} />
|
| 99 |
+
Refresh
|
| 100 |
+
</button>
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
|
| 104 |
+
{/* Notification Toast */}
|
| 105 |
+
{notification && (
|
| 106 |
+
<div className={`mb-6 p-4 rounded-lg flex items-center gap-3 ${notification.type === 'success' ? 'bg-green-900/50 text-green-200 border border-green-800' : 'bg-red-900/50 text-red-200 border border-red-800'}`}>
|
| 107 |
+
{notification.type === 'success' ? <CheckCircle size={20} /> : <AlertCircle size={20} />}
|
| 108 |
+
{notification.message}
|
| 109 |
+
<button onClick={() => setNotification(null)} className="ml-auto hover:opacity-70">✕</button>
|
| 110 |
+
</div>
|
| 111 |
+
)}
|
| 112 |
+
|
| 113 |
+
{/* Controls */}
|
| 114 |
+
<ExportControls
|
| 115 |
+
count={selectedThreads.length}
|
| 116 |
+
onExport={handleExport}
|
| 117 |
+
exporting={exporting}
|
| 118 |
+
progress={exportProgress}
|
| 119 |
+
/>
|
| 120 |
+
|
| 121 |
+
{/* Content Grid */}
|
| 122 |
+
<div className="mt-6">
|
| 123 |
+
{loading ? (
|
| 124 |
+
<div className="flex justify-center py-20">
|
| 125 |
+
<div className="animate-pulse bg-slate-800 h-4 w-4 rounded-full"></div>
|
| 126 |
+
<div className="animate-pulse bg-slate-800 h-4 w-4 rounded-full ml-2"></div>
|
| 127 |
+
<div className="animate-pulse bg-slate-800 h-4 w-4 rounded-full ml-2"></div>
|
| 128 |
+
</div>
|
| 129 |
+
) : (
|
| 130 |
+
<ThreadList
|
| 131 |
+
threads={threads}
|
| 132 |
+
selectedIds={selectedThreads}
|
| 133 |
+
onSelect={toggleSelection}
|
| 134 |
+
/>
|
| 135 |
+
)}
|
| 136 |
+
</div>
|
| 137 |
+
</main>
|
| 138 |
+
</div>
|
| 139 |
+
);
|
| 140 |
+
}
|