Pulastya B commited on
Commit
c0e18bf
·
1 Parent(s): 2f3df85

Trying to add Auth

Browse files
.gitignore CHANGED
@@ -11,6 +11,7 @@ downloads/
11
  eggs/
12
  .eggs/
13
  lib/
 
14
  lib64/
15
  parts/
16
  sdist/
 
11
  eggs/
12
  .eggs/
13
  lib/
14
+ !FRRONTEEEND/lib/
15
  lib64/
16
  parts/
17
  sdist/
FRRONTEEEND/.env.example ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ # Supabase Configuration
2
+ # Get these values from your Supabase project settings: https://app.supabase.com/
3
+ VITE_SUPABASE_URL=your_supabase_project_url
4
+ VITE_SUPABASE_ANON_KEY=your_supabase_anon_key
FRRONTEEEND/App.tsx CHANGED
@@ -1,5 +1,5 @@
1
 
2
- import React, { useState } from 'react';
3
  import { HeroGeometric } from './components/HeroGeometric';
4
  import ProblemSolution from './components/ProblemSolution';
5
  import KeyCapabilities from './components/KeyCapabilities';
@@ -9,9 +9,31 @@ import Footer from './components/Footer';
9
  import { BackgroundPaths } from './components/BackgroundPaths';
10
  import { Logo } from './components/Logo';
11
  import { ChatInterface } from './components/ChatInterface';
 
 
 
12
 
13
- const App: React.FC = () => {
14
- const [view, setView] = useState<'landing' | 'chat'>('landing');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
  if (view === 'chat') {
17
  return <ChatInterface onBack={() => setView('landing')} />;
@@ -28,16 +50,59 @@ const App: React.FC = () => {
28
  </span>
29
  </div>
30
 
31
- <button
32
- onClick={() => setView('chat')}
33
- className="px-5 py-2 bg-white/5 hover:bg-white/10 border border-white/10 rounded-lg text-sm font-medium transition-all"
34
- >
35
- Launch Console
36
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  </nav>
38
 
39
  <main>
40
- <HeroGeometric onChatClick={() => setView('chat')} />
41
  <TechStack />
42
  <ProblemSolution />
43
  <KeyCapabilities />
@@ -56,4 +121,13 @@ const App: React.FC = () => {
56
  );
57
  };
58
 
 
 
 
 
 
 
 
 
 
59
  export default App;
 
1
 
2
+ import React, { useState, useEffect } from 'react';
3
  import { HeroGeometric } from './components/HeroGeometric';
4
  import ProblemSolution from './components/ProblemSolution';
5
  import KeyCapabilities from './components/KeyCapabilities';
 
9
  import { BackgroundPaths } from './components/BackgroundPaths';
10
  import { Logo } from './components/Logo';
11
  import { ChatInterface } from './components/ChatInterface';
12
+ import { AuthPage } from './components/AuthPage';
13
+ import { AuthProvider, useAuth } from './lib/AuthContext';
14
+ import { User, LogOut } from 'lucide-react';
15
 
16
+ // Inner app component that uses auth context
17
+ const AppContent: React.FC = () => {
18
+ const [view, setView] = useState<'landing' | 'chat' | 'auth'>('landing');
19
+ const { user, isAuthenticated, loading, signOut } = useAuth();
20
+ const [showUserMenu, setShowUserMenu] = useState(false);
21
+
22
+ // Handle launch console - redirect to auth if not logged in
23
+ const handleLaunchConsole = () => {
24
+ // Allow both authenticated and guest users
25
+ setView('chat');
26
+ };
27
+
28
+ // Show auth page
29
+ if (view === 'auth') {
30
+ return (
31
+ <AuthPage
32
+ onSuccess={() => setView('chat')}
33
+ onSkip={() => setView('chat')}
34
+ />
35
+ );
36
+ }
37
 
38
  if (view === 'chat') {
39
  return <ChatInterface onBack={() => setView('landing')} />;
 
50
  </span>
51
  </div>
52
 
53
+ <div className="flex items-center gap-3">
54
+ {/* User menu */}
55
+ {isAuthenticated ? (
56
+ <div className="relative">
57
+ <button
58
+ onClick={() => setShowUserMenu(!showUserMenu)}
59
+ className="flex items-center gap-2 px-3 py-2 bg-white/5 hover:bg-white/10 border border-white/10 rounded-lg text-sm transition-all"
60
+ >
61
+ <User className="w-4 h-4" />
62
+ <span className="hidden sm:block max-w-[120px] truncate">
63
+ {user?.email?.split('@')[0]}
64
+ </span>
65
+ </button>
66
+
67
+ {showUserMenu && (
68
+ <div className="absolute right-0 mt-2 w-48 bg-[#1a1a1a] border border-white/10 rounded-lg shadow-xl py-1">
69
+ <div className="px-4 py-2 border-b border-white/10">
70
+ <p className="text-xs text-white/50">Signed in as</p>
71
+ <p className="text-sm text-white truncate">{user?.email}</p>
72
+ </div>
73
+ <button
74
+ onClick={async () => {
75
+ await signOut();
76
+ setShowUserMenu(false);
77
+ }}
78
+ className="w-full flex items-center gap-2 px-4 py-2 text-sm text-red-400 hover:bg-white/5 transition-colors"
79
+ >
80
+ <LogOut className="w-4 h-4" />
81
+ Sign Out
82
+ </button>
83
+ </div>
84
+ )}
85
+ </div>
86
+ ) : (
87
+ <button
88
+ onClick={() => setView('auth')}
89
+ className="px-4 py-2 text-sm text-white/70 hover:text-white transition-colors"
90
+ >
91
+ Sign In
92
+ </button>
93
+ )}
94
+
95
+ <button
96
+ onClick={handleLaunchConsole}
97
+ className="px-5 py-2 bg-white/5 hover:bg-white/10 border border-white/10 rounded-lg text-sm font-medium transition-all"
98
+ >
99
+ Launch Console
100
+ </button>
101
+ </div>
102
  </nav>
103
 
104
  <main>
105
+ <HeroGeometric onChatClick={handleLaunchConsole} />
106
  <TechStack />
107
  <ProblemSolution />
108
  <KeyCapabilities />
 
121
  );
122
  };
123
 
124
+ // Wrap with AuthProvider
125
+ const App: React.FC = () => {
126
+ return (
127
+ <AuthProvider>
128
+ <AppContent />
129
+ </AuthProvider>
130
+ );
131
+ };
132
+
133
  export default App;
FRRONTEEEND/SUPABASE_SETUP.md ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Supabase Authentication & Analytics Setup
2
+
3
+ ## Quick Setup (5 minutes)
4
+
5
+ ### 1. Create Supabase Project
6
+ 1. Go to [https://app.supabase.com/](https://app.supabase.com/)
7
+ 2. Click "New Project"
8
+ 3. Choose organization, name your project (e.g., "ds-agent-analytics")
9
+ 4. Set a strong database password (save it!)
10
+ 5. Choose a region close to your users
11
+ 6. Click "Create new project" and wait ~2 minutes
12
+
13
+ ### 2. Get Your API Keys
14
+ 1. Go to **Settings** → **API**
15
+ 2. Copy:
16
+ - **Project URL**: `https://xxxxx.supabase.co`
17
+ - **anon/public key**: `eyJhbGciOi...` (long string)
18
+
19
+ ### 3. Configure Environment
20
+ Create `.env` file in `FRRONTEEEND/`:
21
+ ```bash
22
+ VITE_SUPABASE_URL=https://your-project-id.supabase.co
23
+ VITE_SUPABASE_ANON_KEY=your-anon-key-here
24
+ ```
25
+
26
+ For HuggingFace Spaces, add these as **Secrets**:
27
+ 1. Go to your Space → Settings → Repository secrets
28
+ 2. Add `VITE_SUPABASE_URL` and `VITE_SUPABASE_ANON_KEY`
29
+
30
+ ### 4. Set Up Database Tables
31
+ 1. Go to **SQL Editor** in Supabase dashboard
32
+ 2. Copy contents of `supabase_schema.sql`
33
+ 3. Click "Run" to create tables and policies
34
+
35
+ ### 5. Enable Authentication Providers (Optional)
36
+
37
+ #### Email (enabled by default)
38
+ - Works out of the box
39
+
40
+ #### Google OAuth
41
+ 1. Go to **Authentication** → **Providers** → **Google**
42
+ 2. Enable it
43
+ 3. Create OAuth credentials at [Google Cloud Console](https://console.cloud.google.com/apis/credentials)
44
+ 4. Add your Supabase callback URL: `https://your-project.supabase.co/auth/v1/callback`
45
+ 5. Copy Client ID and Secret to Supabase
46
+
47
+ #### GitHub OAuth
48
+ 1. Go to **Authentication** → **Providers** → **GitHub**
49
+ 2. Enable it
50
+ 3. Create OAuth App at [GitHub Developer Settings](https://github.com/settings/developers)
51
+ 4. Add callback URL: `https://your-project.supabase.co/auth/v1/callback`
52
+ 5. Copy Client ID and Secret to Supabase
53
+
54
+ ## Features Included
55
+
56
+ ### Authentication
57
+ - ✅ Email/Password sign up & sign in
58
+ - ✅ Google OAuth (optional)
59
+ - ✅ GitHub OAuth (optional)
60
+ - ✅ Persistent sessions
61
+ - ✅ Guest mode (can use without signing in)
62
+
63
+ ### Analytics Tracking
64
+ - ✅ Per-query tracking (user, session, query text, success/failure)
65
+ - ✅ Session tracking (start time, end time, query count)
66
+ - ✅ Browser info capture
67
+ - ✅ Anonymous user support
68
+
69
+ ### Dashboard Views (in Supabase)
70
+ - `daily_active_users` - DAU metrics
71
+ - `popular_queries` - Most common queries
72
+ - `agent_usage_stats` - Which agents are used most
73
+
74
+ ## Viewing Analytics
75
+
76
+ ### Quick Stats in Supabase
77
+ 1. Go to **Table Editor**
78
+ 2. Select `usage_analytics` or `user_sessions`
79
+ 3. Use filters and sorting to analyze data
80
+
81
+ ### SQL Queries
82
+ ```sql
83
+ -- Total users last 7 days
84
+ SELECT COUNT(DISTINCT user_id)
85
+ FROM usage_analytics
86
+ WHERE created_at > NOW() - INTERVAL '7 days';
87
+
88
+ -- Queries per day
89
+ SELECT DATE(created_at), COUNT(*)
90
+ FROM usage_analytics
91
+ GROUP BY DATE(created_at)
92
+ ORDER BY 1 DESC;
93
+
94
+ -- Success rate
95
+ SELECT
96
+ SUM(CASE WHEN success THEN 1 ELSE 0 END)::float / COUNT(*) * 100 as success_rate
97
+ FROM usage_analytics;
98
+ ```
99
+
100
+ ## Free Tier Limits (Plenty for demos!)
101
+ - 50,000 monthly active users
102
+ - 500 MB database storage
103
+ - 1 GB file storage
104
+ - Unlimited API requests
105
+ - 2 projects
106
+
107
+ ## Troubleshooting
108
+
109
+ ### "Invalid API key"
110
+ - Check that your `.env` file has the correct values
111
+ - Make sure you're using the `anon` key, not the `service_role` key
112
+
113
+ ### OAuth not working
114
+ - Verify callback URL is correct in provider settings
115
+ - Check that the provider is enabled in Supabase
116
+
117
+ ### Data not appearing
118
+ - Check browser console for errors
119
+ - Verify RLS policies are created (run the SQL schema)
120
+ - Check Supabase logs for errors
FRRONTEEEND/components/AuthPage.tsx ADDED
@@ -0,0 +1,262 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { motion, AnimatePresence } from 'framer-motion';
3
+ import { Mail, Lock, User, Github, ArrowLeft, Loader2, AlertCircle, CheckCircle } from 'lucide-react';
4
+ import { useAuth } from '../lib/AuthContext';
5
+ import { Logo } from './Logo';
6
+
7
+ interface AuthPageProps {
8
+ onSuccess?: () => void;
9
+ onSkip?: () => void;
10
+ }
11
+
12
+ export const AuthPage: React.FC<AuthPageProps> = ({ onSuccess, onSkip }) => {
13
+ const { signIn, signUp, signInWithGoogle, signInWithGithub } = useAuth();
14
+ const [mode, setMode] = useState<'signin' | 'signup'>('signin');
15
+ const [email, setEmail] = useState('');
16
+ const [password, setPassword] = useState('');
17
+ const [loading, setLoading] = useState(false);
18
+ const [error, setError] = useState<string | null>(null);
19
+ const [success, setSuccess] = useState<string | null>(null);
20
+
21
+ const handleSubmit = async (e: React.FormEvent) => {
22
+ e.preventDefault();
23
+ setLoading(true);
24
+ setError(null);
25
+ setSuccess(null);
26
+
27
+ try {
28
+ if (mode === 'signup') {
29
+ const { error } = await signUp(email, password);
30
+ if (error) {
31
+ setError(error.message);
32
+ } else {
33
+ setSuccess('Check your email to confirm your account!');
34
+ }
35
+ } else {
36
+ const { error } = await signIn(email, password);
37
+ if (error) {
38
+ setError(error.message);
39
+ } else {
40
+ onSuccess?.();
41
+ }
42
+ }
43
+ } catch (err: any) {
44
+ setError(err.message || 'An error occurred');
45
+ } finally {
46
+ setLoading(false);
47
+ }
48
+ };
49
+
50
+ const handleOAuthSignIn = async (provider: 'google' | 'github') => {
51
+ setLoading(true);
52
+ setError(null);
53
+
54
+ try {
55
+ const { error } = provider === 'google' ? await signInWithGoogle() : await signInWithGithub();
56
+ if (error) {
57
+ setError(error.message);
58
+ }
59
+ } catch (err: any) {
60
+ setError(err.message || 'An error occurred');
61
+ } finally {
62
+ setLoading(false);
63
+ }
64
+ };
65
+
66
+ return (
67
+ <div className="min-h-screen bg-[#0a0a0a] flex items-center justify-center p-4">
68
+ {/* Background gradient */}
69
+ <div className="absolute inset-0 overflow-hidden">
70
+ <div className="absolute -top-40 -right-40 w-80 h-80 bg-indigo-500/10 rounded-full blur-3xl" />
71
+ <div className="absolute -bottom-40 -left-40 w-80 h-80 bg-purple-500/10 rounded-full blur-3xl" />
72
+ </div>
73
+
74
+ <motion.div
75
+ initial={{ opacity: 0, y: 20 }}
76
+ animate={{ opacity: 1, y: 0 }}
77
+ className="relative w-full max-w-md"
78
+ >
79
+ {/* Card */}
80
+ <div className="bg-white/[0.03] border border-white/10 rounded-2xl p-8 backdrop-blur-xl shadow-2xl">
81
+ {/* Logo */}
82
+ <div className="flex justify-center mb-6">
83
+ <Logo />
84
+ </div>
85
+
86
+ {/* Title */}
87
+ <div className="text-center mb-8">
88
+ <h1 className="text-2xl font-bold text-white mb-2">
89
+ {mode === 'signin' ? 'Welcome Back' : 'Create Account'}
90
+ </h1>
91
+ <p className="text-white/50 text-sm">
92
+ {mode === 'signin'
93
+ ? 'Sign in to access your data science workflows'
94
+ : 'Join us to start analyzing your data'}
95
+ </p>
96
+ </div>
97
+
98
+ {/* OAuth Buttons */}
99
+ <div className="space-y-3 mb-6">
100
+ <button
101
+ onClick={() => handleOAuthSignIn('google')}
102
+ disabled={loading}
103
+ className="w-full flex items-center justify-center gap-3 px-4 py-3 bg-white/5 hover:bg-white/10 border border-white/10 rounded-xl transition-all text-white font-medium disabled:opacity-50"
104
+ >
105
+ <svg className="w-5 h-5" viewBox="0 0 24 24">
106
+ <path
107
+ fill="currentColor"
108
+ d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
109
+ />
110
+ <path
111
+ fill="currentColor"
112
+ d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
113
+ />
114
+ <path
115
+ fill="currentColor"
116
+ d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
117
+ />
118
+ <path
119
+ fill="currentColor"
120
+ d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
121
+ />
122
+ </svg>
123
+ Continue with Google
124
+ </button>
125
+
126
+ <button
127
+ onClick={() => handleOAuthSignIn('github')}
128
+ disabled={loading}
129
+ className="w-full flex items-center justify-center gap-3 px-4 py-3 bg-white/5 hover:bg-white/10 border border-white/10 rounded-xl transition-all text-white font-medium disabled:opacity-50"
130
+ >
131
+ <Github className="w-5 h-5" />
132
+ Continue with GitHub
133
+ </button>
134
+ </div>
135
+
136
+ {/* Divider */}
137
+ <div className="relative mb-6">
138
+ <div className="absolute inset-0 flex items-center">
139
+ <div className="w-full border-t border-white/10"></div>
140
+ </div>
141
+ <div className="relative flex justify-center text-sm">
142
+ <span className="px-4 bg-[#0a0a0a] text-white/40">or continue with email</span>
143
+ </div>
144
+ </div>
145
+
146
+ {/* Form */}
147
+ <form onSubmit={handleSubmit} className="space-y-4">
148
+ <div>
149
+ <label className="block text-sm font-medium text-white/70 mb-2">Email</label>
150
+ <div className="relative">
151
+ <Mail className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-white/30" />
152
+ <input
153
+ type="email"
154
+ value={email}
155
+ onChange={(e) => setEmail(e.target.value)}
156
+ placeholder="you@example.com"
157
+ required
158
+ className="w-full pl-11 pr-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder-white/30 focus:outline-none focus:border-indigo-500/50 focus:ring-1 focus:ring-indigo-500/50 transition-all"
159
+ />
160
+ </div>
161
+ </div>
162
+
163
+ <div>
164
+ <label className="block text-sm font-medium text-white/70 mb-2">Password</label>
165
+ <div className="relative">
166
+ <Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-white/30" />
167
+ <input
168
+ type="password"
169
+ value={password}
170
+ onChange={(e) => setPassword(e.target.value)}
171
+ placeholder="••••••••"
172
+ required
173
+ minLength={6}
174
+ className="w-full pl-11 pr-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder-white/30 focus:outline-none focus:border-indigo-500/50 focus:ring-1 focus:ring-indigo-500/50 transition-all"
175
+ />
176
+ </div>
177
+ </div>
178
+
179
+ {/* Error Message */}
180
+ <AnimatePresence>
181
+ {error && (
182
+ <motion.div
183
+ initial={{ opacity: 0, y: -10 }}
184
+ animate={{ opacity: 1, y: 0 }}
185
+ exit={{ opacity: 0 }}
186
+ className="flex items-center gap-2 p-3 bg-red-500/10 border border-red-500/20 rounded-xl text-red-400 text-sm"
187
+ >
188
+ <AlertCircle className="w-4 h-4 flex-shrink-0" />
189
+ {error}
190
+ </motion.div>
191
+ )}
192
+ </AnimatePresence>
193
+
194
+ {/* Success Message */}
195
+ <AnimatePresence>
196
+ {success && (
197
+ <motion.div
198
+ initial={{ opacity: 0, y: -10 }}
199
+ animate={{ opacity: 1, y: 0 }}
200
+ exit={{ opacity: 0 }}
201
+ className="flex items-center gap-2 p-3 bg-green-500/10 border border-green-500/20 rounded-xl text-green-400 text-sm"
202
+ >
203
+ <CheckCircle className="w-4 h-4 flex-shrink-0" />
204
+ {success}
205
+ </motion.div>
206
+ )}
207
+ </AnimatePresence>
208
+
209
+ {/* Submit Button */}
210
+ <button
211
+ type="submit"
212
+ disabled={loading}
213
+ className="w-full py-3 bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-500 hover:to-purple-500 text-white font-medium rounded-xl transition-all disabled:opacity-50 flex items-center justify-center gap-2"
214
+ >
215
+ {loading ? (
216
+ <Loader2 className="w-5 h-5 animate-spin" />
217
+ ) : mode === 'signin' ? (
218
+ 'Sign In'
219
+ ) : (
220
+ 'Create Account'
221
+ )}
222
+ </button>
223
+ </form>
224
+
225
+ {/* Toggle Mode */}
226
+ <div className="mt-6 text-center">
227
+ <p className="text-white/50 text-sm">
228
+ {mode === 'signin' ? "Don't have an account? " : 'Already have an account? '}
229
+ <button
230
+ onClick={() => {
231
+ setMode(mode === 'signin' ? 'signup' : 'signin');
232
+ setError(null);
233
+ setSuccess(null);
234
+ }}
235
+ className="text-indigo-400 hover:text-indigo-300 font-medium"
236
+ >
237
+ {mode === 'signin' ? 'Sign Up' : 'Sign In'}
238
+ </button>
239
+ </p>
240
+ </div>
241
+
242
+ {/* Skip Button */}
243
+ {onSkip && (
244
+ <div className="mt-4 text-center">
245
+ <button
246
+ onClick={onSkip}
247
+ className="text-white/30 hover:text-white/50 text-sm transition-colors"
248
+ >
249
+ Continue without signing in →
250
+ </button>
251
+ </div>
252
+ )}
253
+ </div>
254
+
255
+ {/* Footer */}
256
+ <p className="text-center text-white/30 text-xs mt-6">
257
+ By signing in, you agree to our Terms of Service and Privacy Policy
258
+ </p>
259
+ </motion.div>
260
+ </div>
261
+ );
262
+ };
FRRONTEEEND/components/ChatInterface.tsx CHANGED
@@ -1,10 +1,12 @@
1
 
2
  import React, { useState, useRef, useEffect } from 'react';
3
  import { motion, AnimatePresence } from 'framer-motion';
4
- import { Send, Plus, Search, Settings, MoreHorizontal, User, Bot, ArrowLeft, Paperclip, Sparkles, Trash2, X, Upload, Package, FileText, BarChart3, ChevronRight } from 'lucide-react';
5
  import { cn } from '../lib/utils';
6
  import { Logo } from './Logo';
7
  import ReactMarkdown from 'react-markdown';
 
 
8
 
9
  interface Message {
10
  id: string;
@@ -106,6 +108,9 @@ export const ChatInterface: React.FC<{ onBack: () => void }> = ({ onBack }) => {
106
  const eventSourceRef = useRef<EventSource | null>(null);
107
  const processedAnalysisRef = useRef<Set<string>>(new Set()); // Track processed analysis_complete events
108
 
 
 
 
109
  const activeSession = sessions.find(s => s.id === activeSessionId) || sessions[0];
110
 
111
  // Persist sessions to localStorage whenever they change
@@ -409,11 +414,30 @@ export const ChatInterface: React.FC<{ onBack: () => void }> = ({ onBack }) => {
409
  formData.append('use_cache', 'false'); // Disabled to show multi-agent execution
410
  formData.append('max_iterations', '20');
411
 
 
 
 
412
  response = await fetch(`${API_URL}/run-async`, {
413
  method: 'POST',
414
  body: formData
415
  });
416
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  setUploadedFile(null);
418
  } else {
419
  // No file and no backend session - use simple chat endpoint
@@ -709,17 +733,46 @@ export const ChatInterface: React.FC<{ onBack: () => void }> = ({ onBack }) => {
709
  ))}
710
  </div>
711
 
712
- <div className="mt-auto pt-4 border-t border-white/5 flex items-center justify-between px-2">
713
- <button onClick={onBack} className="p-2 hover:bg-white/5 rounded-lg transition-colors text-white/40 hover:text-white">
714
- <ArrowLeft className="w-5 h-5" />
715
- </button>
716
- <div className="flex gap-2">
717
- <button className="p-2 hover:bg-white/5 rounded-lg transition-colors text-white/40 hover:text-white">
718
- <Settings className="w-5 h-5" />
719
- </button>
720
- <button className="p-2 hover:bg-white/5 rounded-lg transition-colors text-white/40 hover:text-white">
721
- <User className="w-5 h-5" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
722
  </button>
 
 
 
 
 
723
  </div>
724
  </div>
725
  </div>
 
1
 
2
  import React, { useState, useRef, useEffect } from 'react';
3
  import { motion, AnimatePresence } from 'framer-motion';
4
+ import { Send, Plus, Search, Settings, MoreHorizontal, User, Bot, ArrowLeft, Paperclip, Sparkles, Trash2, X, Upload, Package, FileText, BarChart3, ChevronRight, LogOut } from 'lucide-react';
5
  import { cn } from '../lib/utils';
6
  import { Logo } from './Logo';
7
  import ReactMarkdown from 'react-markdown';
8
+ import { useAuth } from '../lib/AuthContext';
9
+ import { trackQuery, incrementSessionQueries } from '../lib/supabase';
10
 
11
  interface Message {
12
  id: string;
 
108
  const eventSourceRef = useRef<EventSource | null>(null);
109
  const processedAnalysisRef = useRef<Set<string>>(new Set()); // Track processed analysis_complete events
110
 
111
+ // Auth context for user tracking
112
+ const { user, isAuthenticated, dbSessionId, signOut } = useAuth();
113
+
114
  const activeSession = sessions.find(s => s.id === activeSessionId) || sessions[0];
115
 
116
  // Persist sessions to localStorage whenever they change
 
414
  formData.append('use_cache', 'false'); // Disabled to show multi-agent execution
415
  formData.append('max_iterations', '20');
416
 
417
+ // Track query start time for analytics
418
+ const queryStartTime = Date.now();
419
+
420
  response = await fetch(`${API_URL}/run-async`, {
421
  method: 'POST',
422
  body: formData
423
  });
424
 
425
+ // 📊 Track analytics (non-blocking)
426
+ if (user || dbSessionId) {
427
+ trackQuery({
428
+ user_id: user?.id || 'anonymous',
429
+ user_email: user?.email,
430
+ session_id: sessionKey,
431
+ query: input || 'File analysis',
432
+ success: response.ok,
433
+ error_message: response.ok ? undefined : `HTTP ${response.status}`
434
+ }).catch(console.error);
435
+
436
+ if (dbSessionId) {
437
+ incrementSessionQueries(dbSessionId).catch(console.error);
438
+ }
439
+ }
440
+
441
  setUploadedFile(null);
442
  } else {
443
  // No file and no backend session - use simple chat endpoint
 
733
  ))}
734
  </div>
735
 
736
+ <div className="mt-auto pt-4 border-t border-white/5">
737
+ {/* User info section */}
738
+ {isAuthenticated && user ? (
739
+ <div className="flex items-center gap-3 px-2 py-2 mb-2">
740
+ <div className="w-8 h-8 rounded-full bg-gradient-to-br from-indigo-500 to-purple-600 flex items-center justify-center text-xs font-bold">
741
+ {user.email?.[0]?.toUpperCase() || 'U'}
742
+ </div>
743
+ <div className="flex-1 min-w-0">
744
+ <p className="text-sm text-white truncate">{user.email?.split('@')[0]}</p>
745
+ <p className="text-xs text-white/40 truncate">{user.email}</p>
746
+ </div>
747
+ <button
748
+ onClick={() => signOut()}
749
+ className="p-2 hover:bg-white/5 rounded-lg transition-colors text-white/40 hover:text-red-400"
750
+ title="Sign Out"
751
+ >
752
+ <LogOut className="w-4 h-4" />
753
+ </button>
754
+ </div>
755
+ ) : (
756
+ <div className="flex items-center gap-3 px-2 py-2 mb-2">
757
+ <div className="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center">
758
+ <User className="w-4 h-4 text-white/50" />
759
+ </div>
760
+ <div className="flex-1">
761
+ <p className="text-sm text-white/50">Guest User</p>
762
+ <p className="text-xs text-white/30">Sign in to save history</p>
763
+ </div>
764
+ </div>
765
+ )}
766
+
767
+ <div className="flex items-center justify-between px-2">
768
+ <button onClick={onBack} className="p-2 hover:bg-white/5 rounded-lg transition-colors text-white/40 hover:text-white">
769
+ <ArrowLeft className="w-5 h-5" />
770
  </button>
771
+ <div className="flex gap-2">
772
+ <button className="p-2 hover:bg-white/5 rounded-lg transition-colors text-white/40 hover:text-white">
773
+ <Settings className="w-5 h-5" />
774
+ </button>
775
+ </div>
776
  </div>
777
  </div>
778
  </div>
FRRONTEEEND/lib/AuthContext.tsx ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { createContext, useContext, useEffect, useState } from 'react';
2
+ import { User, Session, AuthChangeEvent } from '@supabase/supabase-js';
3
+ import { supabase, startUserSession, endUserSession } from './supabase';
4
+
5
+ interface AuthContextType {
6
+ user: User | null;
7
+ session: Session | null;
8
+ dbSessionId: string | null;
9
+ loading: boolean;
10
+ signIn: (email: string, password: string) => Promise<{ error: any }>;
11
+ signUp: (email: string, password: string) => Promise<{ error: any }>;
12
+ signInWithGoogle: () => Promise<{ error: any }>;
13
+ signInWithGithub: () => Promise<{ error: any }>;
14
+ signOut: () => Promise<void>;
15
+ isAuthenticated: boolean;
16
+ }
17
+
18
+ const AuthContext = createContext<AuthContextType | undefined>(undefined);
19
+
20
+ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
21
+ const [user, setUser] = useState<User | null>(null);
22
+ const [session, setSession] = useState<Session | null>(null);
23
+ const [dbSessionId, setDbSessionId] = useState<string | null>(null);
24
+ const [loading, setLoading] = useState(true);
25
+
26
+ useEffect(() => {
27
+ // Get initial session
28
+ supabase.auth.getSession().then(({ data: { session } }) => {
29
+ setSession(session);
30
+ setUser(session?.user ?? null);
31
+ setLoading(false);
32
+
33
+ // Start tracking session if user is logged in
34
+ if (session?.user) {
35
+ startUserSession(session.user.id, session.user.email).then((dbSession) => {
36
+ if (dbSession) {
37
+ setDbSessionId(dbSession.id);
38
+ }
39
+ });
40
+ }
41
+ });
42
+
43
+ // Listen for auth changes
44
+ const { data: { subscription } } = supabase.auth.onAuthStateChange(
45
+ async (event: AuthChangeEvent, session) => {
46
+ setSession(session);
47
+ setUser(session?.user ?? null);
48
+ setLoading(false);
49
+
50
+ if (event === 'SIGNED_IN' && session?.user) {
51
+ // Start new tracking session
52
+ const dbSession = await startUserSession(session.user.id, session.user.email);
53
+ if (dbSession) {
54
+ setDbSessionId(dbSession.id);
55
+ }
56
+ } else if (event === 'SIGNED_OUT') {
57
+ // End tracking session
58
+ if (dbSessionId) {
59
+ await endUserSession(dbSessionId);
60
+ setDbSessionId(null);
61
+ }
62
+ }
63
+ }
64
+ );
65
+
66
+ // Cleanup on unmount
67
+ return () => {
68
+ subscription.unsubscribe();
69
+ // End session when component unmounts
70
+ if (dbSessionId) {
71
+ endUserSession(dbSessionId);
72
+ }
73
+ };
74
+ }, []);
75
+
76
+ const signIn = async (email: string, password: string) => {
77
+ const { error } = await supabase.auth.signInWithPassword({ email, password });
78
+ return { error };
79
+ };
80
+
81
+ const signUp = async (email: string, password: string) => {
82
+ const { error } = await supabase.auth.signUp({ email, password });
83
+ return { error };
84
+ };
85
+
86
+ const signInWithGoogle = async () => {
87
+ const { error } = await supabase.auth.signInWithOAuth({
88
+ provider: 'google',
89
+ options: {
90
+ redirectTo: window.location.origin
91
+ }
92
+ });
93
+ return { error };
94
+ };
95
+
96
+ const signInWithGithub = async () => {
97
+ const { error } = await supabase.auth.signInWithOAuth({
98
+ provider: 'github',
99
+ options: {
100
+ redirectTo: window.location.origin
101
+ }
102
+ });
103
+ return { error };
104
+ };
105
+
106
+ const signOut = async () => {
107
+ if (dbSessionId) {
108
+ await endUserSession(dbSessionId);
109
+ setDbSessionId(null);
110
+ }
111
+ await supabase.auth.signOut();
112
+ };
113
+
114
+ return (
115
+ <AuthContext.Provider
116
+ value={{
117
+ user,
118
+ session,
119
+ dbSessionId,
120
+ loading,
121
+ signIn,
122
+ signUp,
123
+ signInWithGoogle,
124
+ signInWithGithub,
125
+ signOut,
126
+ isAuthenticated: !!user
127
+ }}
128
+ >
129
+ {children}
130
+ </AuthContext.Provider>
131
+ );
132
+ };
133
+
134
+ export const useAuth = () => {
135
+ const context = useContext(AuthContext);
136
+ if (context === undefined) {
137
+ throw new Error('useAuth must be used within an AuthProvider');
138
+ }
139
+ return context;
140
+ };
FRRONTEEEND/lib/supabase.ts ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createClient } from '@supabase/supabase-js';
2
+
3
+ // Supabase configuration - these will be loaded from environment variables
4
+ const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || '';
5
+ const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY || '';
6
+
7
+ // Create Supabase client
8
+ export const supabase = createClient(supabaseUrl, supabaseAnonKey);
9
+
10
+ // Types for our analytics
11
+ export interface UsageAnalytics {
12
+ id?: string;
13
+ user_id: string;
14
+ user_email?: string;
15
+ session_id: string;
16
+ query: string;
17
+ agent_used?: string;
18
+ tools_executed?: string[];
19
+ tokens_used?: number;
20
+ duration_ms?: number;
21
+ success: boolean;
22
+ error_message?: string;
23
+ created_at?: string;
24
+ }
25
+
26
+ export interface UserSession {
27
+ id?: string;
28
+ user_id: string;
29
+ user_email?: string;
30
+ started_at: string;
31
+ ended_at?: string;
32
+ queries_count: number;
33
+ browser_info?: string;
34
+ }
35
+
36
+ // Analytics functions
37
+ export const trackQuery = async (analytics: Omit<UsageAnalytics, 'id' | 'created_at'>) => {
38
+ try {
39
+ const { data, error } = await supabase
40
+ .from('usage_analytics')
41
+ .insert([{
42
+ ...analytics,
43
+ created_at: new Date().toISOString()
44
+ }]);
45
+
46
+ if (error) {
47
+ console.error('Failed to track query:', error);
48
+ return null;
49
+ }
50
+ return data;
51
+ } catch (err) {
52
+ console.error('Analytics tracking error:', err);
53
+ return null;
54
+ }
55
+ };
56
+
57
+ export const startUserSession = async (userId: string, userEmail?: string) => {
58
+ try {
59
+ const { data, error } = await supabase
60
+ .from('user_sessions')
61
+ .insert([{
62
+ user_id: userId,
63
+ user_email: userEmail,
64
+ started_at: new Date().toISOString(),
65
+ queries_count: 0,
66
+ browser_info: typeof navigator !== 'undefined' ? navigator.userAgent : null
67
+ }])
68
+ .select()
69
+ .single();
70
+
71
+ if (error) {
72
+ console.error('Failed to start session:', error);
73
+ return null;
74
+ }
75
+ return data;
76
+ } catch (err) {
77
+ console.error('Session tracking error:', err);
78
+ return null;
79
+ }
80
+ };
81
+
82
+ export const endUserSession = async (sessionId: string) => {
83
+ try {
84
+ const { error } = await supabase
85
+ .from('user_sessions')
86
+ .update({ ended_at: new Date().toISOString() })
87
+ .eq('id', sessionId);
88
+
89
+ if (error) {
90
+ console.error('Failed to end session:', error);
91
+ }
92
+ } catch (err) {
93
+ console.error('Session end error:', err);
94
+ }
95
+ };
96
+
97
+ export const incrementSessionQueries = async (sessionId: string) => {
98
+ try {
99
+ // Use RPC for atomic increment
100
+ const { error } = await supabase.rpc('increment_session_queries', {
101
+ session_id: sessionId
102
+ });
103
+
104
+ if (error) {
105
+ // Fallback: fetch and update
106
+ const { data } = await supabase
107
+ .from('user_sessions')
108
+ .select('queries_count')
109
+ .eq('id', sessionId)
110
+ .single();
111
+
112
+ if (data) {
113
+ await supabase
114
+ .from('user_sessions')
115
+ .update({ queries_count: (data.queries_count || 0) + 1 })
116
+ .eq('id', sessionId);
117
+ }
118
+ }
119
+ } catch (err) {
120
+ console.error('Failed to increment queries:', err);
121
+ }
122
+ };
123
+
124
+ // Get usage stats (for admin dashboard)
125
+ export const getUsageStats = async (days: number = 7) => {
126
+ try {
127
+ const startDate = new Date();
128
+ startDate.setDate(startDate.getDate() - days);
129
+
130
+ const { data, error } = await supabase
131
+ .from('usage_analytics')
132
+ .select('*')
133
+ .gte('created_at', startDate.toISOString())
134
+ .order('created_at', { ascending: false });
135
+
136
+ if (error) {
137
+ console.error('Failed to get stats:', error);
138
+ return null;
139
+ }
140
+ return data;
141
+ } catch (err) {
142
+ console.error('Stats fetch error:', err);
143
+ return null;
144
+ }
145
+ };
146
+
147
+ // Get unique users count
148
+ export const getUniqueUsersCount = async (days: number = 7) => {
149
+ try {
150
+ const startDate = new Date();
151
+ startDate.setDate(startDate.getDate() - days);
152
+
153
+ const { data, error } = await supabase
154
+ .from('user_sessions')
155
+ .select('user_id')
156
+ .gte('started_at', startDate.toISOString());
157
+
158
+ if (error) {
159
+ console.error('Failed to get unique users:', error);
160
+ return 0;
161
+ }
162
+
163
+ // Count unique user IDs
164
+ const uniqueUsers = new Set(data?.map(d => d.user_id));
165
+ return uniqueUsers.size;
166
+ } catch (err) {
167
+ console.error('Unique users fetch error:', err);
168
+ return 0;
169
+ }
170
+ };
FRRONTEEEND/package-lock.json CHANGED
@@ -8,6 +8,7 @@
8
  "name": "data-science-agent",
9
  "version": "0.0.0",
10
  "dependencies": {
 
11
  "clsx": "^2.1.1",
12
  "framer-motion": "^12.23.26",
13
  "lucide-react": "^0.562.0",
@@ -1113,6 +1114,86 @@
1113
  "win32"
1114
  ]
1115
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1116
  "node_modules/@types/babel__core": {
1117
  "version": "7.20.5",
1118
  "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -1210,13 +1291,17 @@
1210
  "version": "22.19.3",
1211
  "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz",
1212
  "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==",
1213
- "dev": true,
1214
  "license": "MIT",
1215
- "peer": true,
1216
  "dependencies": {
1217
  "undici-types": "~6.21.0"
1218
  }
1219
  },
 
 
 
 
 
 
1220
  "node_modules/@types/react": {
1221
  "version": "19.2.7",
1222
  "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
@@ -1233,6 +1318,15 @@
1233
  "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
1234
  "license": "MIT"
1235
  },
 
 
 
 
 
 
 
 
 
1236
  "node_modules/@ungap/structured-clone": {
1237
  "version": "1.3.0",
1238
  "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
@@ -1665,6 +1759,15 @@
1665
  "url": "https://opencollective.com/unified"
1666
  }
1667
  },
 
 
 
 
 
 
 
 
 
1668
  "node_modules/inline-style-parser": {
1669
  "version": "0.2.7",
1670
  "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
@@ -2790,7 +2893,6 @@
2790
  "version": "6.21.0",
2791
  "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
2792
  "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
2793
- "dev": true,
2794
  "license": "MIT"
2795
  },
2796
  "node_modules/unified": {
@@ -3015,6 +3117,27 @@
3015
  }
3016
  }
3017
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3018
  "node_modules/yallist": {
3019
  "version": "3.1.1",
3020
  "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
 
8
  "name": "data-science-agent",
9
  "version": "0.0.0",
10
  "dependencies": {
11
+ "@supabase/supabase-js": "^2.93.3",
12
  "clsx": "^2.1.1",
13
  "framer-motion": "^12.23.26",
14
  "lucide-react": "^0.562.0",
 
1114
  "win32"
1115
  ]
1116
  },
1117
+ "node_modules/@supabase/auth-js": {
1118
+ "version": "2.93.3",
1119
+ "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.93.3.tgz",
1120
+ "integrity": "sha512-JdnkHZPKexVGSNONtu89RHU4bxz3X9kxx+f5ZnR5osoCIX+vs/MckwWRPZEybAEvlJXt5xjomDb3IB876QCxWQ==",
1121
+ "license": "MIT",
1122
+ "dependencies": {
1123
+ "tslib": "2.8.1"
1124
+ },
1125
+ "engines": {
1126
+ "node": ">=20.0.0"
1127
+ }
1128
+ },
1129
+ "node_modules/@supabase/functions-js": {
1130
+ "version": "2.93.3",
1131
+ "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.93.3.tgz",
1132
+ "integrity": "sha512-qWO0gHNDm/5jRjROv/nv9L6sYabCWS1kzorOLUv3kqCwRvEJLYZga93ppJPrZwOgoZfXmJzvpjY8fODA4HQfBw==",
1133
+ "license": "MIT",
1134
+ "dependencies": {
1135
+ "tslib": "2.8.1"
1136
+ },
1137
+ "engines": {
1138
+ "node": ">=20.0.0"
1139
+ }
1140
+ },
1141
+ "node_modules/@supabase/postgrest-js": {
1142
+ "version": "2.93.3",
1143
+ "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.93.3.tgz",
1144
+ "integrity": "sha512-+iJ96g94skO2e4clsRSmEXg22NUOjh9BziapsJSAvnB1grOBf/BA8vGtCHjNOA+Z6lvKXL1jwBqcL9+fS1W/Lg==",
1145
+ "license": "MIT",
1146
+ "dependencies": {
1147
+ "tslib": "2.8.1"
1148
+ },
1149
+ "engines": {
1150
+ "node": ">=20.0.0"
1151
+ }
1152
+ },
1153
+ "node_modules/@supabase/realtime-js": {
1154
+ "version": "2.93.3",
1155
+ "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.93.3.tgz",
1156
+ "integrity": "sha512-gnYpcFzwy8IkezRP4CDbT5I8jOsiOjrWrqTY1B+7jIriXsnpifmlM6RRjLBm9oD7OwPG0/WksniGPdKW67sXOA==",
1157
+ "license": "MIT",
1158
+ "dependencies": {
1159
+ "@types/phoenix": "^1.6.6",
1160
+ "@types/ws": "^8.18.1",
1161
+ "tslib": "2.8.1",
1162
+ "ws": "^8.18.2"
1163
+ },
1164
+ "engines": {
1165
+ "node": ">=20.0.0"
1166
+ }
1167
+ },
1168
+ "node_modules/@supabase/storage-js": {
1169
+ "version": "2.93.3",
1170
+ "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.93.3.tgz",
1171
+ "integrity": "sha512-cw4qXiLrx3apglDM02Tx/w/stvFlrkKocC6vCvuFAz3JtVEl1zH8MUfDQDTH59kJAQVaVdbewrMWSoBob7REnA==",
1172
+ "license": "MIT",
1173
+ "dependencies": {
1174
+ "iceberg-js": "^0.8.1",
1175
+ "tslib": "2.8.1"
1176
+ },
1177
+ "engines": {
1178
+ "node": ">=20.0.0"
1179
+ }
1180
+ },
1181
+ "node_modules/@supabase/supabase-js": {
1182
+ "version": "2.93.3",
1183
+ "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.93.3.tgz",
1184
+ "integrity": "sha512-paUqEqdBI9ztr/4bbMoCgeJ6M8ZTm2fpfjSOlzarPuzYveKFM20ZfDZqUpi9CFfYagYj5Iv3m3ztUjaI9/tM1w==",
1185
+ "license": "MIT",
1186
+ "dependencies": {
1187
+ "@supabase/auth-js": "2.93.3",
1188
+ "@supabase/functions-js": "2.93.3",
1189
+ "@supabase/postgrest-js": "2.93.3",
1190
+ "@supabase/realtime-js": "2.93.3",
1191
+ "@supabase/storage-js": "2.93.3"
1192
+ },
1193
+ "engines": {
1194
+ "node": ">=20.0.0"
1195
+ }
1196
+ },
1197
  "node_modules/@types/babel__core": {
1198
  "version": "7.20.5",
1199
  "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
 
1291
  "version": "22.19.3",
1292
  "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz",
1293
  "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==",
 
1294
  "license": "MIT",
 
1295
  "dependencies": {
1296
  "undici-types": "~6.21.0"
1297
  }
1298
  },
1299
+ "node_modules/@types/phoenix": {
1300
+ "version": "1.6.7",
1301
+ "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz",
1302
+ "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==",
1303
+ "license": "MIT"
1304
+ },
1305
  "node_modules/@types/react": {
1306
  "version": "19.2.7",
1307
  "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
 
1318
  "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
1319
  "license": "MIT"
1320
  },
1321
+ "node_modules/@types/ws": {
1322
+ "version": "8.18.1",
1323
+ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
1324
+ "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
1325
+ "license": "MIT",
1326
+ "dependencies": {
1327
+ "@types/node": "*"
1328
+ }
1329
+ },
1330
  "node_modules/@ungap/structured-clone": {
1331
  "version": "1.3.0",
1332
  "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
 
1759
  "url": "https://opencollective.com/unified"
1760
  }
1761
  },
1762
+ "node_modules/iceberg-js": {
1763
+ "version": "0.8.1",
1764
+ "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
1765
+ "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==",
1766
+ "license": "MIT",
1767
+ "engines": {
1768
+ "node": ">=20.0.0"
1769
+ }
1770
+ },
1771
  "node_modules/inline-style-parser": {
1772
  "version": "0.2.7",
1773
  "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
 
2893
  "version": "6.21.0",
2894
  "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
2895
  "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
 
2896
  "license": "MIT"
2897
  },
2898
  "node_modules/unified": {
 
3117
  }
3118
  }
3119
  },
3120
+ "node_modules/ws": {
3121
+ "version": "8.19.0",
3122
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
3123
+ "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
3124
+ "license": "MIT",
3125
+ "engines": {
3126
+ "node": ">=10.0.0"
3127
+ },
3128
+ "peerDependencies": {
3129
+ "bufferutil": "^4.0.1",
3130
+ "utf-8-validate": ">=5.0.2"
3131
+ },
3132
+ "peerDependenciesMeta": {
3133
+ "bufferutil": {
3134
+ "optional": true
3135
+ },
3136
+ "utf-8-validate": {
3137
+ "optional": true
3138
+ }
3139
+ }
3140
+ },
3141
  "node_modules/yallist": {
3142
  "version": "3.1.1",
3143
  "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
FRRONTEEEND/package.json CHANGED
@@ -9,13 +9,14 @@
9
  "preview": "vite preview"
10
  },
11
  "dependencies": {
12
- "react": "^19.2.3",
13
- "react-dom": "^19.2.3",
14
  "clsx": "^2.1.1",
15
- "tailwind-merge": "^3.4.0",
16
  "framer-motion": "^12.23.26",
17
  "lucide-react": "^0.562.0",
18
- "react-markdown": "^9.0.1"
 
 
 
19
  },
20
  "devDependencies": {
21
  "@types/node": "^22.14.0",
 
9
  "preview": "vite preview"
10
  },
11
  "dependencies": {
12
+ "@supabase/supabase-js": "^2.93.3",
 
13
  "clsx": "^2.1.1",
 
14
  "framer-motion": "^12.23.26",
15
  "lucide-react": "^0.562.0",
16
+ "react": "^19.2.3",
17
+ "react-dom": "^19.2.3",
18
+ "react-markdown": "^9.0.1",
19
+ "tailwind-merge": "^3.4.0"
20
  },
21
  "devDependencies": {
22
  "@types/node": "^22.14.0",
FRRONTEEEND/supabase_schema.sql ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- =====================================================
2
+ -- Supabase Database Schema for Data Science Agent Analytics
3
+ -- =====================================================
4
+ -- Run this in your Supabase SQL Editor: https://app.supabase.com/project/_/sql
5
+
6
+ -- 1. Usage Analytics Table
7
+ -- Tracks individual queries/requests made by users
8
+ CREATE TABLE IF NOT EXISTS usage_analytics (
9
+ id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
10
+ user_id TEXT NOT NULL,
11
+ user_email TEXT,
12
+ session_id TEXT NOT NULL,
13
+ query TEXT NOT NULL,
14
+ agent_used TEXT,
15
+ tools_executed TEXT[],
16
+ tokens_used INTEGER,
17
+ duration_ms INTEGER,
18
+ success BOOLEAN DEFAULT true,
19
+ error_message TEXT,
20
+ created_at TIMESTAMPTZ DEFAULT NOW()
21
+ );
22
+
23
+ -- 2. User Sessions Table
24
+ -- Tracks user sessions for engagement metrics
25
+ CREATE TABLE IF NOT EXISTS user_sessions (
26
+ id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
27
+ user_id TEXT NOT NULL,
28
+ user_email TEXT,
29
+ started_at TIMESTAMPTZ DEFAULT NOW(),
30
+ ended_at TIMESTAMPTZ,
31
+ queries_count INTEGER DEFAULT 0,
32
+ browser_info TEXT
33
+ );
34
+
35
+ -- 3. Indexes for performance
36
+ CREATE INDEX IF NOT EXISTS idx_usage_analytics_user_id ON usage_analytics(user_id);
37
+ CREATE INDEX IF NOT EXISTS idx_usage_analytics_created_at ON usage_analytics(created_at);
38
+ CREATE INDEX IF NOT EXISTS idx_usage_analytics_session_id ON usage_analytics(session_id);
39
+ CREATE INDEX IF NOT EXISTS idx_user_sessions_user_id ON user_sessions(user_id);
40
+ CREATE INDEX IF NOT EXISTS idx_user_sessions_started_at ON user_sessions(started_at);
41
+
42
+ -- 4. Function to increment session query count atomically
43
+ CREATE OR REPLACE FUNCTION increment_session_queries(session_id UUID)
44
+ RETURNS VOID AS $$
45
+ BEGIN
46
+ UPDATE user_sessions
47
+ SET queries_count = queries_count + 1
48
+ WHERE id = session_id;
49
+ END;
50
+ $$ LANGUAGE plpgsql;
51
+
52
+ -- 5. Enable Row Level Security (RLS)
53
+ ALTER TABLE usage_analytics ENABLE ROW LEVEL SECURITY;
54
+ ALTER TABLE user_sessions ENABLE ROW LEVEL SECURITY;
55
+
56
+ -- 6. RLS Policies - Allow authenticated users to insert their own data
57
+ -- Policy for usage_analytics
58
+ CREATE POLICY "Users can insert their own analytics" ON usage_analytics
59
+ FOR INSERT WITH CHECK (true);
60
+
61
+ CREATE POLICY "Users can view their own analytics" ON usage_analytics
62
+ FOR SELECT USING (auth.uid()::text = user_id OR user_id = 'anonymous');
63
+
64
+ -- Policy for user_sessions
65
+ CREATE POLICY "Users can insert their own sessions" ON user_sessions
66
+ FOR INSERT WITH CHECK (true);
67
+
68
+ CREATE POLICY "Users can update their own sessions" ON user_sessions
69
+ FOR UPDATE USING (auth.uid()::text = user_id OR user_id = 'anonymous');
70
+
71
+ CREATE POLICY "Users can view their own sessions" ON user_sessions
72
+ FOR SELECT USING (auth.uid()::text = user_id OR user_id = 'anonymous');
73
+
74
+ -- 7. Helpful Views for Analytics Dashboard
75
+
76
+ -- Daily active users
77
+ CREATE OR REPLACE VIEW daily_active_users AS
78
+ SELECT
79
+ DATE(created_at) as date,
80
+ COUNT(DISTINCT user_id) as unique_users,
81
+ COUNT(*) as total_queries
82
+ FROM usage_analytics
83
+ GROUP BY DATE(created_at)
84
+ ORDER BY date DESC;
85
+
86
+ -- Popular queries
87
+ CREATE OR REPLACE VIEW popular_queries AS
88
+ SELECT
89
+ query,
90
+ COUNT(*) as count,
91
+ COUNT(DISTINCT user_id) as unique_users
92
+ FROM usage_analytics
93
+ WHERE created_at > NOW() - INTERVAL '7 days'
94
+ GROUP BY query
95
+ ORDER BY count DESC
96
+ LIMIT 50;
97
+
98
+ -- Agent usage stats
99
+ CREATE OR REPLACE VIEW agent_usage_stats AS
100
+ SELECT
101
+ agent_used,
102
+ COUNT(*) as total_uses,
103
+ AVG(duration_ms) as avg_duration_ms,
104
+ SUM(CASE WHEN success THEN 1 ELSE 0 END)::float / COUNT(*) * 100 as success_rate
105
+ FROM usage_analytics
106
+ WHERE agent_used IS NOT NULL
107
+ GROUP BY agent_used
108
+ ORDER BY total_uses DESC;
109
+
110
+ -- =====================================================
111
+ -- SETUP INSTRUCTIONS:
112
+ -- =====================================================
113
+ -- 1. Go to https://app.supabase.com/ and create a new project
114
+ -- 2. Go to Settings > API to get your Project URL and anon key
115
+ -- 3. Create a .env file in FRRONTEEEND/ with:
116
+ -- VITE_SUPABASE_URL=your_project_url
117
+ -- VITE_SUPABASE_ANON_KEY=your_anon_key
118
+ -- 4. Go to Authentication > Providers and enable:
119
+ -- - Email (enabled by default)
120
+ -- - Google (optional - need OAuth credentials)
121
+ -- - GitHub (optional - need OAuth app)
122
+ -- 5. Run this SQL in the SQL Editor
123
+ -- 6. Done! Your analytics will start tracking automatically
124
+ -- =====================================================