-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathApp.tsx
More file actions
155 lines (136 loc) · 5.94 KB
/
App.tsx
File metadata and controls
155 lines (136 loc) · 5.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
import React, { useState } from 'react';
import { UploadView } from './components/UploadView';
import { Dashboard } from './components/Dashboard';
import { AppState, Bookmark, OrganizedBookmark } from './types';
import { organizeBookmarksWithGemini } from './services/geminiService';
import { Loader2, Sparkles } from 'lucide-react';
const App: React.FC = () => {
const [appState, setAppState] = useState<AppState>(AppState.UPLOAD);
const [rawBookmarks, setRawBookmarks] = useState<Bookmark[]>([]);
const [organizedBookmarks, setOrganizedBookmarks] = useState<OrganizedBookmark[]>([]);
const [loadingProgress, setLoadingProgress] = useState<string>('Initializing AI...');
const handleBookmarksLoaded = async (bookmarks: Bookmark[]) => {
// Pre-process for duplicates
const urlSet = new Set<string>();
const processedBookmarks = bookmarks.map(b => {
// Normalize URL slightly (remove trailing slash for comparison)
const normalizedUrl = b.url.replace(/\/$/, '');
const isDuplicate = urlSet.has(normalizedUrl);
urlSet.add(normalizedUrl);
return { ...b, isDuplicate };
});
setRawBookmarks(processedBookmarks);
setAppState(AppState.ANALYZING);
try {
setLoadingProgress('Gemini 3 Flash is analyzing and double-checking your structure...');
const response = await organizeBookmarksWithGemini(processedBookmarks);
// Merge AI response with original bookmark data
const merged: OrganizedBookmark[] = [];
// Map response back to original objects
// Note: We only processed a subset, so we need to handle the ones not returned gracefully if any
response.forEach(item => {
const original = processedBookmarks.find(b => b.id === item.id);
if (original) {
merged.push({
...original,
category: item.category,
subcategory: item.subcategory
});
}
});
// Handle leftovers (those not processed due to limits)
const processedIds = new Set(merged.map(m => m.id));
processedBookmarks.forEach(b => {
if (!processedIds.has(b.id)) {
// Fallback for items not processed by AI (e.g. beyond the 200 limit)
merged.push({
...b,
category: 'Manual Review Needed',
subcategory: 'Pending'
});
}
});
setOrganizedBookmarks(merged);
setAppState(AppState.DASHBOARD);
} catch (error) {
console.error(error);
setAppState(AppState.ERROR);
}
};
const handleReset = () => {
setRawBookmarks([]);
setOrganizedBookmarks([]);
setAppState(AppState.UPLOAD);
};
const handleRemoveBookmarks = (idsToRemove: string[]) => {
setOrganizedBookmarks(prev => prev.filter(b => !idsToRemove.includes(b.id)));
};
return (
<div className="min-h-screen bg-slate-950 text-slate-100 font-sans selection:bg-blue-500/30">
{appState === AppState.UPLOAD && (
<UploadView onBookmarksLoaded={handleBookmarksLoaded} />
)}
{appState === AppState.ANALYZING && (
<div className="flex flex-col items-center justify-center min-h-screen p-8 text-center animate-fade-in relative overflow-hidden">
{/* Background Decoration */}
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[500px] h-[500px] bg-blue-500/10 rounded-full blur-3xl pointer-events-none"></div>
<div className="relative mb-8">
<div className="absolute inset-0 bg-blue-500 rounded-full blur-2xl opacity-20 animate-pulse"></div>
<div className="relative bg-slate-900 p-6 rounded-2xl shadow-xl border border-slate-800">
<Loader2 className="w-12 h-12 text-blue-500 animate-spin" />
</div>
</div>
<h2 className="text-3xl font-bold text-slate-100 mb-2 flex items-center gap-3">
<Sparkles className="text-yellow-400 w-8 h-8" />
Organizing your digital life
</h2>
<p className="text-slate-400 max-w-md mb-8 text-lg">
{loadingProgress}
</p>
<div className="w-64 space-y-3">
<div className="h-2 bg-slate-800 rounded-full overflow-hidden">
<div className="h-full bg-blue-500 animate-progress origin-left shadow-[0_0_10px_rgba(59,130,246,0.5)]"></div>
</div>
<p className="text-xs text-slate-500 font-medium">This may take a moment as we verify every link...</p>
</div>
<style>{`
@keyframes progress {
0% { width: 0%; }
40% { width: 50%; }
80% { width: 80%; }
100% { width: 95%; }
}
.animate-progress {
animation: progress 8s cubic-bezier(0.1, 0.7, 1.0, 0.1) forwards;
}
`}</style>
</div>
)}
{appState === AppState.DASHBOARD && (
<Dashboard
bookmarks={organizedBookmarks}
onReset={handleReset}
onRemoveBookmarks={handleRemoveBookmarks}
/>
)}
{appState === AppState.ERROR && (
<div className="flex flex-col items-center justify-center min-h-screen p-8 text-center bg-slate-950">
<div className="bg-red-900/20 p-6 rounded-full mb-6 border border-red-900/30">
<span className="text-4xl">😕</span>
</div>
<h2 className="text-2xl font-bold text-slate-100 mb-2">Something went wrong</h2>
<p className="text-slate-400 mb-6 max-w-md">
We couldn't process your bookmarks. This might be due to an API issue or the file format.
</p>
<button
onClick={handleReset}
className="px-6 py-3 bg-slate-800 text-white rounded-xl hover:bg-slate-700 transition-colors font-medium border border-slate-700"
>
Try Again
</button>
</div>
)}
</div>
);
};
export default App;