@realtimex/email-automator 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/api/server.ts +4 -8
  2. package/api/src/config/index.ts +6 -3
  3. package/bin/email-automator-setup.js +2 -3
  4. package/bin/email-automator.js +7 -11
  5. package/dist/api/server.js +109 -0
  6. package/dist/api/src/config/index.js +88 -0
  7. package/dist/api/src/middleware/auth.js +119 -0
  8. package/dist/api/src/middleware/errorHandler.js +78 -0
  9. package/dist/api/src/middleware/index.js +4 -0
  10. package/dist/api/src/middleware/rateLimit.js +57 -0
  11. package/dist/api/src/middleware/validation.js +111 -0
  12. package/dist/api/src/routes/actions.js +173 -0
  13. package/dist/api/src/routes/auth.js +106 -0
  14. package/dist/api/src/routes/emails.js +100 -0
  15. package/dist/api/src/routes/health.js +33 -0
  16. package/dist/api/src/routes/index.js +19 -0
  17. package/dist/api/src/routes/migrate.js +61 -0
  18. package/dist/api/src/routes/rules.js +104 -0
  19. package/dist/api/src/routes/settings.js +178 -0
  20. package/dist/api/src/routes/sync.js +118 -0
  21. package/dist/api/src/services/eventLogger.js +41 -0
  22. package/dist/api/src/services/gmail.js +350 -0
  23. package/dist/api/src/services/intelligence.js +243 -0
  24. package/dist/api/src/services/microsoft.js +256 -0
  25. package/dist/api/src/services/processor.js +503 -0
  26. package/dist/api/src/services/scheduler.js +210 -0
  27. package/dist/api/src/services/supabase.js +59 -0
  28. package/dist/api/src/utils/contentCleaner.js +94 -0
  29. package/dist/api/src/utils/crypto.js +68 -0
  30. package/dist/api/src/utils/logger.js +119 -0
  31. package/package.json +5 -5
  32. package/src/App.tsx +0 -622
  33. package/src/components/AccountSettings.tsx +0 -310
  34. package/src/components/AccountSettingsPage.tsx +0 -390
  35. package/src/components/Configuration.tsx +0 -1345
  36. package/src/components/Dashboard.tsx +0 -940
  37. package/src/components/ErrorBoundary.tsx +0 -71
  38. package/src/components/LiveTerminal.tsx +0 -308
  39. package/src/components/LoadingSpinner.tsx +0 -39
  40. package/src/components/Login.tsx +0 -371
  41. package/src/components/Logo.tsx +0 -57
  42. package/src/components/SetupWizard.tsx +0 -388
  43. package/src/components/Toast.tsx +0 -109
  44. package/src/components/migration/MigrationBanner.tsx +0 -97
  45. package/src/components/migration/MigrationModal.tsx +0 -458
  46. package/src/components/migration/MigrationPulseIndicator.tsx +0 -38
  47. package/src/components/mode-toggle.tsx +0 -24
  48. package/src/components/theme-provider.tsx +0 -72
  49. package/src/components/ui/alert.tsx +0 -66
  50. package/src/components/ui/button.tsx +0 -57
  51. package/src/components/ui/card.tsx +0 -75
  52. package/src/components/ui/dialog.tsx +0 -133
  53. package/src/components/ui/input.tsx +0 -22
  54. package/src/components/ui/label.tsx +0 -24
  55. package/src/components/ui/otp-input.tsx +0 -184
  56. package/src/context/AppContext.tsx +0 -422
  57. package/src/context/MigrationContext.tsx +0 -53
  58. package/src/context/TerminalContext.tsx +0 -31
  59. package/src/core/actions.ts +0 -76
  60. package/src/core/auth.ts +0 -108
  61. package/src/core/intelligence.ts +0 -76
  62. package/src/core/processor.ts +0 -112
  63. package/src/hooks/useRealtimeEmails.ts +0 -111
  64. package/src/index.css +0 -140
  65. package/src/lib/api-config.ts +0 -42
  66. package/src/lib/api-old.ts +0 -228
  67. package/src/lib/api.ts +0 -421
  68. package/src/lib/migration-check.ts +0 -264
  69. package/src/lib/sounds.ts +0 -120
  70. package/src/lib/supabase-config.ts +0 -117
  71. package/src/lib/supabase.ts +0 -28
  72. package/src/lib/types.ts +0 -166
  73. package/src/lib/utils.ts +0 -6
  74. package/src/main.tsx +0 -10
@@ -1,264 +0,0 @@
1
- /**
2
- * Migration Version Check Utility
3
- *
4
- * This module provides functions to detect if the database needs migration
5
- * by comparing the app version with the database schema version.
6
- */
7
-
8
- import type { SupabaseClient } from '@supabase/supabase-js';
9
-
10
- /**
11
- * Get the current app version from package.json
12
- */
13
- export const APP_VERSION = import.meta.env.VITE_APP_VERSION;
14
-
15
- /**
16
- * Get the latest migration timestamp bundled with this app
17
- * Format: YYYYMMDDHHMMSS (e.g., "20251229213735")
18
- */
19
- export const LATEST_MIGRATION_TIMESTAMP =
20
- import.meta.env.VITE_LATEST_MIGRATION_TIMESTAMP;
21
-
22
- /**
23
- * Compare two semantic versions (e.g., "0.31.0" vs "0.30.0")
24
- * Returns:
25
- * 1 if v1 > v2
26
- * 0 if v1 === v2
27
- * -1 if v1 < v2
28
- */
29
- export function compareSemver(v1: string, v2: string): number {
30
- const parts1 = v1.split('.').map(Number);
31
- const parts2 = v2.split('.').map(Number);
32
-
33
- for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
34
- const num1 = parts1[i] || 0;
35
- const num2 = parts2[i] || 0;
36
-
37
- if (num1 > num2) return 1;
38
- if (num1 < num2) return -1;
39
- }
40
-
41
- return 0;
42
- }
43
-
44
- /**
45
- * Database migration info
46
- */
47
- export interface DatabaseMigrationInfo {
48
- version: string | null;
49
- latestMigrationTimestamp: string | null;
50
- }
51
-
52
- /**
53
- * Get the latest applied migration info from the database
54
- * Uses Supabase's internal migration tracking via a database function
55
- */
56
- export async function getDatabaseMigrationInfo(
57
- supabase: SupabaseClient,
58
- ): Promise<DatabaseMigrationInfo> {
59
- try {
60
- // Call database function that queries Supabase's internal migration table
61
- // This is automatically updated by `supabase db push`
62
- const { data, error } = await supabase
63
- .rpc('get_latest_migration_timestamp');
64
-
65
- if (error) {
66
- console.warn('Could not get latest migration timestamp:', error.message);
67
- return { version: null, latestMigrationTimestamp: null };
68
- }
69
-
70
- // The returned value IS the migration timestamp (e.g., "20251230082455")
71
- const latestTimestamp = data || null;
72
-
73
- return {
74
- version: APP_VERSION, // Use app version for display
75
- latestMigrationTimestamp: latestTimestamp,
76
- };
77
- } catch (error) {
78
- console.error('Error checking database migration info:', error);
79
- return { version: null, latestMigrationTimestamp: null };
80
- }
81
- }
82
-
83
- /**
84
- * Migration status result
85
- */
86
- export interface MigrationStatus {
87
- /** Whether migration is needed */
88
- needsMigration: boolean;
89
- /** Current app version */
90
- appVersion: string;
91
- /** Database schema version (null if unknown) */
92
- dbVersion: string | null;
93
- /** Human-readable status message */
94
- message: string;
95
- }
96
-
97
- /**
98
- * Check if database migration is needed
99
- *
100
- * Uses timestamp comparison for accurate migration detection:
101
- * - Compares app's latest migration timestamp with DB's latest migration timestamp
102
- * - If app timestamp > DB timestamp → new migrations available
103
- *
104
- * Fallback to SemVer comparison if DB lacks timestamp tracking (legacy schemas).
105
- *
106
- * @param supabase - Supabase client instance
107
- * @returns Promise<MigrationStatus>
108
- */
109
- export async function checkMigrationStatus(
110
- supabase: SupabaseClient,
111
- ): Promise<MigrationStatus> {
112
- const appVersion = APP_VERSION;
113
- const appMigrationTimestamp = LATEST_MIGRATION_TIMESTAMP;
114
- const dbInfo = await getDatabaseMigrationInfo(supabase);
115
-
116
- console.log('[Migration Check]', {
117
- appVersion,
118
- appMigrationTimestamp,
119
- dbVersion: dbInfo.version,
120
- dbMigrationTimestamp: dbInfo.latestMigrationTimestamp,
121
- });
122
-
123
- // 1. Critical failure to determine app state
124
- if (appMigrationTimestamp === 'unknown') {
125
- return {
126
- needsMigration: true,
127
- appVersion,
128
- dbVersion: dbInfo.version,
129
- message: `App migration info missing. Migration to v${appVersion} likely needed.`,
130
- };
131
- }
132
-
133
- // 2. Database has timestamp tracking (Modern)
134
- // Check for valid timestamp (not empty/whitespace)
135
- if (
136
- dbInfo.latestMigrationTimestamp &&
137
- dbInfo.latestMigrationTimestamp.trim() !== ''
138
- ) {
139
- const appTimestamp = appMigrationTimestamp;
140
- const dbTimestamp = dbInfo.latestMigrationTimestamp;
141
-
142
- if (appTimestamp > dbTimestamp) {
143
- return {
144
- needsMigration: true,
145
- appVersion,
146
- dbVersion: dbInfo.version,
147
- message: `New migrations available. Database is at ${dbTimestamp}, app has ${appTimestamp}.`,
148
- };
149
- } else if (appTimestamp < dbTimestamp) {
150
- console.warn('[Migration Check] DB is ahead of app - possible downgrade');
151
- return {
152
- needsMigration: false,
153
- appVersion,
154
- dbVersion: dbInfo.version,
155
- message: `Database (${dbTimestamp}) is ahead of app (${appTimestamp}).`,
156
- };
157
- } else {
158
- console.log('[Migration Check] Timestamps match - database is up-to-date');
159
- return {
160
- needsMigration: false,
161
- appVersion,
162
- dbVersion: dbInfo.version,
163
- message: `Database schema is up-to-date.`,
164
- };
165
- }
166
- }
167
-
168
- // 3. Database has version but NO timestamp (Legacy Schema)
169
- // Fallback to SemVer comparison
170
- if (dbInfo.version) {
171
- console.log(
172
- '[Migration Check] Legacy DB detected (no timestamp). Falling back to SemVer.',
173
- );
174
- const comparison = compareSemver(appVersion, dbInfo.version);
175
-
176
- if (comparison > 0) {
177
- // App version is newer - definitely needs migration
178
- return {
179
- needsMigration: true,
180
- appVersion,
181
- dbVersion: dbInfo.version,
182
- message: `Database schema (v${dbInfo.version}) is outdated. Migration to v${appVersion} required.`,
183
- };
184
- } else if (comparison === 0) {
185
- // Versions match BUT we can't verify migrations without timestamps
186
- // Be pessimistic: force migration to upgrade to modern timestamp tracking
187
- console.warn(
188
- '[Migration Check] Legacy DB with matching version - forcing migration to add timestamp tracking',
189
- );
190
- return {
191
- needsMigration: true,
192
- appVersion,
193
- dbVersion: dbInfo.version,
194
- message: `Database lacks timestamp tracking. Please run migration to upgrade to modern schema (v${appVersion}).`,
195
- };
196
- } else {
197
- // DB version is ahead of app version
198
- return {
199
- needsMigration: false,
200
- appVersion,
201
- dbVersion: dbInfo.version,
202
- message: `Database version (v${dbInfo.version}) is ahead of app (v${appVersion}).`,
203
- };
204
- }
205
- }
206
-
207
- // 4. No DB info at all (Fresh DB or Error)
208
- console.log('[Migration Check] No DB info found - assuming migration needed');
209
- return {
210
- needsMigration: true,
211
- appVersion,
212
- dbVersion: null,
213
- message: `Database schema unknown. Migration required to v${appVersion}.`,
214
- };
215
- }
216
-
217
- /**
218
- * LocalStorage key for migration reminder dismissal
219
- */
220
- const MIGRATION_REMINDER_KEY = 'email_automator_migration_reminder_dismissed_at';
221
-
222
- /**
223
- * Check if user has dismissed the migration reminder recently
224
- *
225
- * @param hoursToWait - Hours to wait before showing reminder again (default: 24)
226
- * @returns true if reminder was dismissed within the time window
227
- */
228
- export function isMigrationReminderDismissed(hoursToWait = 24): boolean {
229
- try {
230
- const dismissedAt = localStorage.getItem(MIGRATION_REMINDER_KEY);
231
- if (!dismissedAt) return false;
232
-
233
- const dismissedTime = new Date(dismissedAt).getTime();
234
- const now = Date.now();
235
- const hoursSinceDismissal = (now - dismissedTime) / (1000 * 60 * 60);
236
-
237
- return hoursSinceDismissal < hoursToWait;
238
- } catch (error) {
239
- console.error('Error checking migration reminder:', error);
240
- return false;
241
- }
242
- }
243
-
244
- /**
245
- * Mark the migration reminder as dismissed
246
- */
247
- export function dismissMigrationReminder(): void {
248
- try {
249
- localStorage.setItem(MIGRATION_REMINDER_KEY, new Date().toISOString());
250
- } catch (error) {
251
- console.error('Error dismissing migration reminder:', error);
252
- }
253
- }
254
-
255
- /**
256
- * Clear the migration reminder dismissal (useful after successful migration)
257
- */
258
- export function clearMigrationReminderDismissal(): void {
259
- try {
260
- localStorage.removeItem(MIGRATION_REMINDER_KEY);
261
- } catch (error) {
262
- console.error('Error clearing migration reminder:', error);
263
- }
264
- }
package/src/lib/sounds.ts DELETED
@@ -1,120 +0,0 @@
1
- /**
2
- * Sound and Haptic Manager for Email Automator
3
- * Uses Web Audio API to synthesize sounds without external assets.
4
- */
5
-
6
- class SoundManager {
7
- private ctx: AudioContext | null = null;
8
- private enabled: boolean = false;
9
-
10
- constructor() {
11
- // Initialize enabled state from localStorage
12
- this.enabled = localStorage.getItem('ea_sounds_enabled') === 'true';
13
- }
14
-
15
- setEnabled(enabled: boolean) {
16
- this.enabled = enabled;
17
- localStorage.setItem('ea_sounds_enabled', enabled ? 'true' : 'none');
18
- }
19
-
20
- isEnabled(): boolean {
21
- return this.enabled;
22
- }
23
-
24
- private initCtx() {
25
- if (!this.ctx) {
26
- this.ctx = new (window.AudioContext || (window as any).webkitAudioContext)();
27
- }
28
- if (this.ctx.state === 'suspended') {
29
- this.ctx.resume();
30
- }
31
- return this.ctx;
32
- }
33
-
34
- /**
35
- * Subtle haptic feedback for supported devices
36
- */
37
- haptic(pattern: number | number[] = 10) {
38
- if (typeof navigator !== 'undefined' && navigator.vibrate) {
39
- navigator.vibrate(pattern);
40
- }
41
- }
42
-
43
- /**
44
- * Soft chime for new emails
45
- */
46
- playNotify() {
47
- if (!this.enabled) return;
48
- const ctx = this.initCtx();
49
- const osc = ctx.createOscillator();
50
- const gain = ctx.createGain();
51
-
52
- osc.type = 'sine';
53
- osc.frequency.setValueAtTime(880, ctx.currentTime); // A5
54
- osc.frequency.exponentialRampToValueAtTime(440, ctx.currentTime + 0.1);
55
-
56
- gain.gain.setValueAtTime(0, ctx.currentTime);
57
- gain.gain.linearRampToValueAtTime(0.1, ctx.currentTime + 0.01);
58
- gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.3);
59
-
60
- osc.connect(gain);
61
- gain.connect(ctx.destination);
62
-
63
- osc.start();
64
- osc.stop(ctx.currentTime + 0.3);
65
- this.haptic(10);
66
- }
67
-
68
- /**
69
- * Distinct tone for High Priority emails
70
- */
71
- playAlert() {
72
- if (!this.enabled) return;
73
- const ctx = this.initCtx();
74
-
75
- const playTone = (freq: number, start: number) => {
76
- const osc = ctx.createOscillator();
77
- const gain = ctx.createGain();
78
- osc.type = 'sine';
79
- osc.frequency.setValueAtTime(freq, ctx.currentTime + start);
80
- gain.gain.setValueAtTime(0, ctx.currentTime + start);
81
- gain.gain.linearRampToValueAtTime(0.1, ctx.currentTime + start + 0.05);
82
- gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + start + 0.4);
83
- osc.connect(gain);
84
- gain.connect(ctx.destination);
85
- osc.start(ctx.currentTime + start);
86
- osc.stop(ctx.currentTime + start + 0.4);
87
- };
88
-
89
- playTone(660, 0); // E5
90
- playTone(880, 0.1); // A5
91
- this.haptic([20, 50, 20]);
92
- }
93
-
94
- /**
95
- * Soft success sound for completed actions
96
- */
97
- playSuccess() {
98
- if (!this.enabled) return;
99
- const ctx = this.initCtx();
100
- const osc = ctx.createOscillator();
101
- const gain = ctx.createGain();
102
-
103
- osc.type = 'triangle';
104
- osc.frequency.setValueAtTime(523.25, ctx.currentTime); // C5
105
- osc.frequency.exponentialRampToValueAtTime(1046.50, ctx.currentTime + 0.1); // C6
106
-
107
- gain.gain.setValueAtTime(0, ctx.currentTime);
108
- gain.gain.linearRampToValueAtTime(0.05, ctx.currentTime + 0.02);
109
- gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + 0.2);
110
-
111
- osc.connect(gain);
112
- gain.connect(ctx.destination);
113
-
114
- osc.start();
115
- osc.stop(ctx.currentTime + 0.2);
116
- this.haptic(15);
117
- }
118
- }
119
-
120
- export const sounds = new SoundManager();
@@ -1,117 +0,0 @@
1
- import { createClient } from '@supabase/supabase-js';
2
-
3
- const STORAGE_KEY = 'email_automator_supabase_config';
4
-
5
- export interface SupabaseConfig {
6
- url: string;
7
- anonKey: string;
8
- }
9
-
10
- export function getSupabaseConfig(): SupabaseConfig | null {
11
- try {
12
- const stored = localStorage.getItem(STORAGE_KEY);
13
- if (stored) {
14
- return JSON.parse(stored);
15
- }
16
- } catch (error) {
17
- console.error('Error reading Supabase config:', error);
18
- }
19
-
20
- // Fallback to env vars
21
- const url = import.meta.env.VITE_SUPABASE_URL;
22
- const anonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
23
-
24
- if (
25
- url &&
26
- anonKey &&
27
- url !== 'your_supabase_url' &&
28
- anonKey !== 'your_supabase_anon_key' &&
29
- url.startsWith('http')
30
- ) {
31
- return { url, anonKey };
32
- }
33
-
34
- return null;
35
- }
36
-
37
- export function saveSupabaseConfig(config: SupabaseConfig): void {
38
- try {
39
- localStorage.setItem(STORAGE_KEY, JSON.stringify(config));
40
- } catch (error) {
41
- console.error('Error saving Supabase config:', error);
42
- }
43
- }
44
-
45
- export function clearSupabaseConfig(): void {
46
- try {
47
- localStorage.removeItem(STORAGE_KEY);
48
- } catch (error) {
49
- console.error('Error clearing Supabase config:', error);
50
- }
51
- }
52
-
53
- /**
54
- * Validate Supabase connection
55
- */
56
- export async function validateSupabaseConnection(
57
- url: string,
58
- anonKey: string
59
- ): Promise<{ valid: boolean; error?: string }> {
60
- try {
61
- // Basic URL validation
62
- if (!url.startsWith('http://') && !url.startsWith('https://')) {
63
- return { valid: false, error: 'Invalid URL format' };
64
- }
65
-
66
- // Basic API key validation (supports both JWT anon keys and publishable keys)
67
- const isJwtKey = anonKey.startsWith('eyJ');
68
- const isPublishableKey = anonKey.startsWith('sb_publishable_');
69
-
70
- if (!isJwtKey && !isPublishableKey) {
71
- return { valid: false, error: 'Invalid API key format (must be anon or publishable key)' };
72
- }
73
-
74
- // Skip network validation for publishable keys as they might not be standard JWTs
75
- // compatible with the Postgrest root endpoint check
76
- if (isPublishableKey) {
77
- return { valid: true };
78
- }
79
-
80
- // Test connection by making a simple request
81
- const response = await fetch(`${url}/rest/v1/`, {
82
- method: 'GET',
83
- headers: {
84
- apikey: anonKey,
85
- Authorization: `Bearer ${anonKey}`,
86
- },
87
- });
88
-
89
- if (!response.ok) {
90
- if (response.status === 401 || response.status === 403) {
91
- return { valid: false, error: 'Invalid API key' };
92
- }
93
- return { valid: false, error: `Connection failed: ${response.statusText}` };
94
- }
95
-
96
- return { valid: true };
97
- } catch (error) {
98
- return {
99
- valid: false,
100
- error: error instanceof Error ? error.message : 'Connection failed',
101
- };
102
- }
103
- }
104
-
105
- /**
106
- * Get Supabase config source (for display purposes)
107
- */
108
- export function getConfigSource(): 'ui' | 'env' | 'none' {
109
- const stored = localStorage.getItem(STORAGE_KEY);
110
- if (stored) return 'ui';
111
-
112
- const url = import.meta.env.VITE_SUPABASE_URL;
113
- const anonKey = import.meta.env.VITE_SUPABASE_ANON_KEY;
114
- if (url && anonKey) return 'env';
115
-
116
- return 'none';
117
- }
@@ -1,28 +0,0 @@
1
- import { createClient, type SupabaseClient } from '@supabase/supabase-js';
2
- import { getSupabaseConfig } from './supabase-config';
3
-
4
- // Create client immediately to ensure session restoration happens early
5
- // This is critical for auth session persistence across page refreshes
6
- function createSupabaseClient(): SupabaseClient {
7
- const config = getSupabaseConfig();
8
-
9
- if (!config || !config.url?.startsWith('http')) {
10
- // Return a placeholder client that will never be used
11
- // (App.tsx will show setup wizard before this is accessed)
12
- console.info('[Supabase] Setup required: Waiting for configuration...');
13
- return createClient('https://placeholder.supabase.co', 'placeholder-key');
14
- }
15
-
16
- return createClient(config.url, config.anonKey, {
17
- auth: {
18
- // Ensure session is persisted and restored from localStorage
19
- persistSession: true,
20
- autoRefreshToken: true,
21
- detectSessionInUrl: true,
22
- },
23
- });
24
- }
25
-
26
- // Create client immediately on module load (not lazy!)
27
- // This ensures session restoration from localStorage happens before any auth checks
28
- export const supabase = createSupabaseClient();
package/src/lib/types.ts DELETED
@@ -1,166 +0,0 @@
1
- // Database types
2
- export interface Profile {
3
- id: string;
4
- first_name: string | null;
5
- last_name: string | null;
6
- email: string | null;
7
- avatar_url: string | null;
8
- is_admin: boolean;
9
- created_at: string;
10
- updated_at: string;
11
- }
12
-
13
- export interface EmailAccount {
14
- id: string;
15
- user_id: string;
16
- provider: 'gmail' | 'outlook';
17
- email_address: string;
18
- is_active: boolean;
19
- last_sync_checkpoint?: string | null;
20
- sync_start_date?: string | null;
21
- sync_max_emails_per_run?: number;
22
- last_sync_at?: string | null;
23
- last_sync_status?: 'idle' | 'syncing' | 'success' | 'error';
24
- last_sync_error?: string | null;
25
- created_at: string;
26
- updated_at: string;
27
- }
28
-
29
- export interface Email {
30
- id: string;
31
- account_id: string;
32
- external_id: string;
33
- subject: string | null;
34
- sender: string | null;
35
- recipient: string | null;
36
- date: string | null;
37
- body_snippet: string | null;
38
- category: EmailCategory | null;
39
- is_useless: boolean;
40
- ai_analysis: EmailAnalysis | null;
41
- suggested_action?: EmailAction | null; // Deprecated
42
- suggested_actions?: EmailAction[];
43
- action_taken?: EmailAction | null; // Deprecated
44
- actions_taken?: EmailAction[];
45
- created_at: string;
46
- email_accounts?: EmailAccount;
47
- }
48
-
49
- export interface Rule {
50
- id: string;
51
- user_id: string;
52
- name: string;
53
- condition: RuleCondition;
54
- action: 'delete' | 'archive' | 'draft' | 'read' | 'star';
55
- instructions?: string;
56
- attachments?: RuleAttachment[];
57
- is_enabled: boolean;
58
- is_system?: boolean; // New flag for pre-defined rules
59
- created_at: string;
60
- }
61
-
62
- export interface RuleCondition {
63
- category?: EmailCategory;
64
- is_useless?: boolean;
65
- sender_contains?: string;
66
- sender_domain?: string;
67
- sender_email?: string;
68
- subject_contains?: string;
69
- body_contains?: string;
70
- older_than_days?: number;
71
- priority?: Priority;
72
- sentiment?: Sentiment;
73
- suggested_actions?: EmailAction[];
74
- }
75
-
76
- export interface RuleAttachment {
77
- name: string;
78
- path: string;
79
- type: string;
80
- size: number;
81
- }
82
-
83
- export interface UserSettings {
84
- id?: string;
85
- user_id?: string;
86
- llm_model: string | null;
87
- llm_base_url: string | null;
88
- llm_api_key: string | null;
89
- sync_interval_minutes: number;
90
- preferences?: Record<string, any>;
91
- }
92
-
93
- export interface Integration {
94
- id: string;
95
- user_id: string;
96
- provider: 'google' | 'microsoft' | 'openai';
97
- credentials: Record<string, any>;
98
- is_enabled: boolean;
99
- created_at: string;
100
- updated_at: string;
101
- }
102
-
103
- export interface ProcessingLog {
104
- id: string;
105
- user_id: string;
106
- status: 'running' | 'success' | 'failed';
107
- started_at: string;
108
- completed_at: string | null;
109
- emails_processed: number;
110
- emails_deleted: number;
111
- emails_drafted: number;
112
- error_message: string | null;
113
- }
114
-
115
- export interface ProcessingEvent {
116
- id: string;
117
- run_id: string;
118
- email_id?: string | null;
119
- event_type: 'info' | 'analysis' | 'action' | 'error';
120
- agent_state: string;
121
- details?: any;
122
- created_at: string;
123
- }
124
-
125
- // Enums
126
- export type EmailCategory = 'spam' | 'newsletter' | 'support' | 'client' | 'internal' | 'personal' | 'other';
127
- export type EmailAction = 'none' | 'delete' | 'archive' | 'reply' | 'flag' | 'draft';
128
- export type Sentiment = 'Positive' | 'Neutral' | 'Negative';
129
- export type Priority = 'High' | 'Medium' | 'Low';
130
-
131
- // AI Analysis
132
- export interface EmailAnalysis {
133
- summary: string;
134
- category: EmailCategory;
135
- sentiment: Sentiment;
136
- is_useless: boolean;
137
- suggested_action: EmailAction;
138
- draft_response?: string;
139
- priority: Priority;
140
- key_points?: string[];
141
- action_items?: string[];
142
- }
143
-
144
- // Stats
145
- export interface Stats {
146
- totalEmails: number;
147
- categoryCounts: Record<string, number>;
148
- actionCounts: Record<string, number>;
149
- uselessCount: number;
150
- accountCount: number;
151
- accountsByProvider: Record<string, number>;
152
- recentSyncs: ProcessingLog[];
153
- }
154
-
155
- // API Response types
156
- export interface ApiError {
157
- code: string;
158
- message: string;
159
- }
160
-
161
- export interface PaginatedResponse<T> {
162
- data: T[];
163
- total: number;
164
- limit: number;
165
- offset: number;
166
- }
package/src/lib/utils.ts DELETED
@@ -1,6 +0,0 @@
1
- import { type ClassValue, clsx } from "clsx"
2
- import { twMerge } from "tailwind-merge"
3
-
4
- export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs))
6
- }