@realtimex/email-automator 2.2.0 → 2.2.1
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.
- package/api/server.ts +0 -6
- package/api/src/config/index.ts +3 -0
- package/bin/email-automator-setup.js +2 -3
- package/bin/email-automator.js +23 -7
- package/package.json +1 -2
- package/src/App.tsx +0 -622
- package/src/components/AccountSettings.tsx +0 -310
- package/src/components/AccountSettingsPage.tsx +0 -390
- package/src/components/Configuration.tsx +0 -1345
- package/src/components/Dashboard.tsx +0 -940
- package/src/components/ErrorBoundary.tsx +0 -71
- package/src/components/LiveTerminal.tsx +0 -308
- package/src/components/LoadingSpinner.tsx +0 -39
- package/src/components/Login.tsx +0 -371
- package/src/components/Logo.tsx +0 -57
- package/src/components/SetupWizard.tsx +0 -388
- package/src/components/Toast.tsx +0 -109
- package/src/components/migration/MigrationBanner.tsx +0 -97
- package/src/components/migration/MigrationModal.tsx +0 -458
- package/src/components/migration/MigrationPulseIndicator.tsx +0 -38
- package/src/components/mode-toggle.tsx +0 -24
- package/src/components/theme-provider.tsx +0 -72
- package/src/components/ui/alert.tsx +0 -66
- package/src/components/ui/button.tsx +0 -57
- package/src/components/ui/card.tsx +0 -75
- package/src/components/ui/dialog.tsx +0 -133
- package/src/components/ui/input.tsx +0 -22
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/otp-input.tsx +0 -184
- package/src/context/AppContext.tsx +0 -422
- package/src/context/MigrationContext.tsx +0 -53
- package/src/context/TerminalContext.tsx +0 -31
- package/src/core/actions.ts +0 -76
- package/src/core/auth.ts +0 -108
- package/src/core/intelligence.ts +0 -76
- package/src/core/processor.ts +0 -112
- package/src/hooks/useRealtimeEmails.ts +0 -111
- package/src/index.css +0 -140
- package/src/lib/api-config.ts +0 -42
- package/src/lib/api-old.ts +0 -228
- package/src/lib/api.ts +0 -421
- package/src/lib/migration-check.ts +0 -264
- package/src/lib/sounds.ts +0 -120
- package/src/lib/supabase-config.ts +0 -117
- package/src/lib/supabase.ts +0 -28
- package/src/lib/types.ts +0 -166
- package/src/lib/utils.ts +0 -6
- package/src/main.tsx +0 -10
package/src/core/intelligence.ts
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import OpenAI from 'openai';
|
|
2
|
-
import Instructor from '@instructor-ai/instructor';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
|
|
5
|
-
// Define the schema for email analysis
|
|
6
|
-
export const EmailAnalysisSchema = z.object({
|
|
7
|
-
summary: z.string().describe("A brief summary of the email content"),
|
|
8
|
-
category: z.enum(['spam', 'newsletter', 'support', 'client', 'internal', 'personal', 'other'])
|
|
9
|
-
.describe("The category of the email"),
|
|
10
|
-
sentiment: z.enum(['Positive', 'Neutral', 'Negative'])
|
|
11
|
-
.describe("The emotional tone of the email"),
|
|
12
|
-
is_useless: z.boolean()
|
|
13
|
-
.describe("Whether the email is considered useless (spam, newsletter, etc.)"),
|
|
14
|
-
suggested_action: z.enum(['none', 'delete', 'archive', 'reply', 'flag'])
|
|
15
|
-
.describe("The recommended next action"),
|
|
16
|
-
draft_response: z.string().optional()
|
|
17
|
-
.describe("A suggested draft response if the action is 'reply'"),
|
|
18
|
-
priority: z.enum(['High', 'Medium', 'Low'])
|
|
19
|
-
.describe("The urgency of the email")
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
export type EmailAnalysis = z.infer<typeof EmailAnalysisSchema>;
|
|
23
|
-
|
|
24
|
-
export class IntelligenceLayer {
|
|
25
|
-
private client;
|
|
26
|
-
|
|
27
|
-
constructor() {
|
|
28
|
-
if (!process.env.LLM_API_KEY) {
|
|
29
|
-
console.warn('LLM_API_KEY is missing. AI analysis will not work.');
|
|
30
|
-
this.client = null as any;
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const oai = new OpenAI({
|
|
35
|
-
apiKey: process.env.LLM_API_KEY,
|
|
36
|
-
baseURL: process.env.LLM_BASE_URL,
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
this.client = Instructor({
|
|
40
|
-
client: oai,
|
|
41
|
-
mode: 'JSON'
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async analyzeEmail(content: string, context: { subject: string; sender: string; date: string }): Promise<EmailAnalysis | null> {
|
|
46
|
-
try {
|
|
47
|
-
const response = await this.client.chat.completions.create({
|
|
48
|
-
model: process.env.LLM_MODEL || 'gpt-4o-mini',
|
|
49
|
-
messages: [
|
|
50
|
-
{
|
|
51
|
-
role: 'system',
|
|
52
|
-
content: `You are an AI Email Assistant. Analyze the provided email and extract structured information.
|
|
53
|
-
Context Date: ${new Date().toISOString()}
|
|
54
|
-
Subject: ${context.subject}
|
|
55
|
-
From: ${context.sender}
|
|
56
|
-
Date: ${context.date}`
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
role: 'user',
|
|
60
|
-
content: content
|
|
61
|
-
}
|
|
62
|
-
],
|
|
63
|
-
response_model: {
|
|
64
|
-
schema: EmailAnalysisSchema,
|
|
65
|
-
name: "EmailAnalysis"
|
|
66
|
-
},
|
|
67
|
-
max_retries: 3
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
return response;
|
|
71
|
-
} catch (error) {
|
|
72
|
-
console.error('AI Analysis Error:', error);
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
package/src/core/processor.ts
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { google } from 'googleapis';
|
|
2
|
-
import { IntelligenceLayer } from './intelligence';
|
|
3
|
-
|
|
4
|
-
export class EmailProcessor {
|
|
5
|
-
private ai: IntelligenceLayer;
|
|
6
|
-
private supabase: any;
|
|
7
|
-
|
|
8
|
-
constructor(supabaseClient?: any) {
|
|
9
|
-
this.ai = new IntelligenceLayer();
|
|
10
|
-
this.supabase = supabaseClient;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
async syncAccount(accountId: string) {
|
|
14
|
-
if (!this.supabase) throw new Error('Supabase client not configured');
|
|
15
|
-
|
|
16
|
-
// 1. Fetch account details
|
|
17
|
-
const { data: account, error: accError } = await this.supabase
|
|
18
|
-
.from('email_accounts')
|
|
19
|
-
.select('*')
|
|
20
|
-
.eq('id', accountId)
|
|
21
|
-
.single();
|
|
22
|
-
|
|
23
|
-
if (accError || !account) throw accError || new Error('Account not found');
|
|
24
|
-
|
|
25
|
-
// 2. Setup client based on provider
|
|
26
|
-
if (account.provider === 'gmail') {
|
|
27
|
-
await this.syncGmail(account);
|
|
28
|
-
} else {
|
|
29
|
-
// TODO: Implement Outlook sync
|
|
30
|
-
console.log('Outlook sync not implemented yet');
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
private async syncGmail(account: any) {
|
|
35
|
-
const auth = new google.auth.OAuth2(
|
|
36
|
-
process.env.GMAIL_CLIENT_ID,
|
|
37
|
-
process.env.GMAIL_CLIENT_SECRET
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
auth.setCredentials({
|
|
41
|
-
access_token: account.access_token,
|
|
42
|
-
refresh_token: account.refresh_token,
|
|
43
|
-
expiry_date: account.token_expires_at ? new Date(account.token_expires_at).getTime() : undefined
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
const gmail = google.gmail({ version: 'v1', auth });
|
|
47
|
-
|
|
48
|
-
// 3. List messages (simplification: last 20)
|
|
49
|
-
const response = await gmail.users.messages.list({
|
|
50
|
-
userId: 'me',
|
|
51
|
-
maxResults: 20
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const messages = response.data.messages || [];
|
|
55
|
-
|
|
56
|
-
for (const msg of messages) {
|
|
57
|
-
if (!msg.id) continue;
|
|
58
|
-
|
|
59
|
-
// Check if already processed
|
|
60
|
-
const { data: existing } = await this.supabase
|
|
61
|
-
.from('emails')
|
|
62
|
-
.select('id')
|
|
63
|
-
.eq('account_id', account.id)
|
|
64
|
-
.eq('external_id', msg.id)
|
|
65
|
-
.single();
|
|
66
|
-
|
|
67
|
-
if (existing) continue;
|
|
68
|
-
|
|
69
|
-
// 4. Get message details
|
|
70
|
-
const detail = await gmail.users.messages.get({
|
|
71
|
-
userId: 'me',
|
|
72
|
-
id: msg.id
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const payload = detail.data.payload;
|
|
76
|
-
const headers = payload?.headers || [];
|
|
77
|
-
const subject = headers.find(h => h.name === 'Subject')?.value || 'No Subject';
|
|
78
|
-
const sender = headers.find(h => h.name === 'From')?.value || 'Unknown';
|
|
79
|
-
const date = headers.find(h => h.name === 'Date')?.value || '';
|
|
80
|
-
|
|
81
|
-
// Extract body (very simplified)
|
|
82
|
-
let body = '';
|
|
83
|
-
if (payload?.parts) {
|
|
84
|
-
body = Buffer.from(payload.parts[0].body?.data || '', 'base64').toString();
|
|
85
|
-
} else {
|
|
86
|
-
body = Buffer.from(payload?.body?.data || '', 'base64').toString();
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// 5. Analyze with AI
|
|
90
|
-
const analysis = await this.ai.analyzeEmail(body, { subject, sender, date });
|
|
91
|
-
|
|
92
|
-
// 6. Save to Supabase
|
|
93
|
-
if (analysis) {
|
|
94
|
-
await this.supabase.from('emails').insert({
|
|
95
|
-
account_id: account.id,
|
|
96
|
-
external_id: msg.id,
|
|
97
|
-
subject,
|
|
98
|
-
sender,
|
|
99
|
-
recipient: 'me', // TODO: extract from headers
|
|
100
|
-
date: date ? new Date(date).toISOString() : null,
|
|
101
|
-
body_snippet: body.substring(0, 500),
|
|
102
|
-
category: analysis.category,
|
|
103
|
-
is_useless: analysis.is_useless,
|
|
104
|
-
ai_analysis: analysis,
|
|
105
|
-
suggested_action: analysis.suggested_action
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// TODO: Execute automation rules here
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react';
|
|
2
|
-
import { supabase } from '../lib/supabase';
|
|
3
|
-
import { Email } from '../lib/types';
|
|
4
|
-
|
|
5
|
-
interface UseRealtimeEmailsOptions {
|
|
6
|
-
userId?: string;
|
|
7
|
-
onInsert?: (email: Email) => void;
|
|
8
|
-
onUpdate?: (email: Email) => void;
|
|
9
|
-
onDelete?: (emailId: string) => void;
|
|
10
|
-
enabled?: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function useRealtimeEmails({
|
|
14
|
-
userId,
|
|
15
|
-
onInsert,
|
|
16
|
-
onUpdate,
|
|
17
|
-
onDelete,
|
|
18
|
-
enabled = true,
|
|
19
|
-
}: UseRealtimeEmailsOptions) {
|
|
20
|
-
const channelRef = useRef<ReturnType<typeof supabase.channel> | null>(null);
|
|
21
|
-
|
|
22
|
-
useEffect(() => {
|
|
23
|
-
if (!enabled || !userId) return;
|
|
24
|
-
|
|
25
|
-
// Subscribe to emails table changes
|
|
26
|
-
const channel = supabase
|
|
27
|
-
.channel('emails-realtime')
|
|
28
|
-
.on(
|
|
29
|
-
'postgres_changes',
|
|
30
|
-
{
|
|
31
|
-
event: 'INSERT',
|
|
32
|
-
schema: 'public',
|
|
33
|
-
table: 'emails',
|
|
34
|
-
},
|
|
35
|
-
(payload) => {
|
|
36
|
-
onInsert?.(payload.new as Email);
|
|
37
|
-
}
|
|
38
|
-
)
|
|
39
|
-
.on(
|
|
40
|
-
'postgres_changes',
|
|
41
|
-
{
|
|
42
|
-
event: 'UPDATE',
|
|
43
|
-
schema: 'public',
|
|
44
|
-
table: 'emails',
|
|
45
|
-
},
|
|
46
|
-
(payload) => {
|
|
47
|
-
onUpdate?.(payload.new as Email);
|
|
48
|
-
}
|
|
49
|
-
)
|
|
50
|
-
.on(
|
|
51
|
-
'postgres_changes',
|
|
52
|
-
{
|
|
53
|
-
event: 'DELETE',
|
|
54
|
-
schema: 'public',
|
|
55
|
-
table: 'emails',
|
|
56
|
-
},
|
|
57
|
-
(payload) => {
|
|
58
|
-
onDelete?.(payload.old.id);
|
|
59
|
-
}
|
|
60
|
-
)
|
|
61
|
-
.subscribe();
|
|
62
|
-
|
|
63
|
-
channelRef.current = channel;
|
|
64
|
-
|
|
65
|
-
return () => {
|
|
66
|
-
if (channelRef.current) {
|
|
67
|
-
supabase.removeChannel(channelRef.current);
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
}, [userId, enabled, onInsert, onUpdate, onDelete]);
|
|
71
|
-
|
|
72
|
-
return {
|
|
73
|
-
isSubscribed: !!channelRef.current,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function useRealtimeProcessingLogs({
|
|
78
|
-
userId,
|
|
79
|
-
onNewLog,
|
|
80
|
-
enabled = true,
|
|
81
|
-
}: {
|
|
82
|
-
userId?: string;
|
|
83
|
-
onNewLog?: (log: any) => void;
|
|
84
|
-
enabled?: boolean;
|
|
85
|
-
}) {
|
|
86
|
-
useEffect(() => {
|
|
87
|
-
if (!enabled || !userId) return;
|
|
88
|
-
|
|
89
|
-
const channel = supabase
|
|
90
|
-
.channel('processing-logs-realtime')
|
|
91
|
-
.on(
|
|
92
|
-
'postgres_changes',
|
|
93
|
-
{
|
|
94
|
-
event: '*',
|
|
95
|
-
schema: 'public',
|
|
96
|
-
table: 'processing_logs',
|
|
97
|
-
filter: `user_id=eq.${userId}`,
|
|
98
|
-
},
|
|
99
|
-
(payload) => {
|
|
100
|
-
if (payload.eventType === 'INSERT' || payload.eventType === 'UPDATE') {
|
|
101
|
-
onNewLog?.(payload.new);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
)
|
|
105
|
-
.subscribe();
|
|
106
|
-
|
|
107
|
-
return () => {
|
|
108
|
-
supabase.removeChannel(channel);
|
|
109
|
-
};
|
|
110
|
-
}, [userId, enabled, onNewLog]);
|
|
111
|
-
}
|
package/src/index.css
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
|
2
|
-
@import 'tailwindcss';
|
|
3
|
-
|
|
4
|
-
@theme {
|
|
5
|
-
--color-border: oklch(var(--border));
|
|
6
|
-
--color-input: oklch(var(--input));
|
|
7
|
-
--color-ring: oklch(var(--ring));
|
|
8
|
-
--color-background: oklch(var(--background));
|
|
9
|
-
--color-foreground: oklch(var(--foreground));
|
|
10
|
-
|
|
11
|
-
--color-primary: oklch(var(--primary));
|
|
12
|
-
--color-primary-foreground: oklch(var(--primary-foreground));
|
|
13
|
-
|
|
14
|
-
--color-secondary: oklch(var(--secondary));
|
|
15
|
-
--color-secondary-foreground: oklch(var(--secondary-foreground));
|
|
16
|
-
|
|
17
|
-
--color-destructive: oklch(var(--destructive));
|
|
18
|
-
--color-destructive-foreground: oklch(var(--destructive-foreground));
|
|
19
|
-
|
|
20
|
-
--color-muted: oklch(var(--muted));
|
|
21
|
-
--color-muted-foreground: oklch(var(--muted-foreground));
|
|
22
|
-
|
|
23
|
-
--color-accent: oklch(var(--accent));
|
|
24
|
-
--color-accent-foreground: oklch(var(--accent-foreground));
|
|
25
|
-
|
|
26
|
-
--color-popover: oklch(var(--popover));
|
|
27
|
-
--color-popover-foreground: oklch(var(--popover-foreground));
|
|
28
|
-
|
|
29
|
-
--color-card: oklch(var(--card));
|
|
30
|
-
--color-card-foreground: oklch(var(--card-foreground));
|
|
31
|
-
|
|
32
|
-
--radius-lg: var(--radius);
|
|
33
|
-
--radius-md: calc(var(--radius) - 2px);
|
|
34
|
-
--radius-sm: calc(var(--radius) - 4px);
|
|
35
|
-
|
|
36
|
-
--animate-accordion-down: accordion-down 0.2s ease-out;
|
|
37
|
-
--animate-accordion-up: accordion-up 0.2s ease-out;
|
|
38
|
-
|
|
39
|
-
@keyframes accordion-down {
|
|
40
|
-
from {
|
|
41
|
-
height: 0;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
to {
|
|
45
|
-
height: var(--radix-accordion-content-height);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
@keyframes accordion-up {
|
|
50
|
-
from {
|
|
51
|
-
height: var(--radix-accordion-content-height);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
to {
|
|
55
|
-
height: 0;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
@utility container {
|
|
61
|
-
margin-inline: auto;
|
|
62
|
-
padding-inline: 2rem;
|
|
63
|
-
|
|
64
|
-
@media (width >=--theme(--breakpoint-sm)) {
|
|
65
|
-
max-width: none;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
@media (width >=1400px) {
|
|
69
|
-
max-width: 1400px;
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
@layer base {
|
|
74
|
-
:root {
|
|
75
|
-
/* Light Mode - OKLCH */
|
|
76
|
-
--background: 1 0 0;
|
|
77
|
-
/* Pure White */
|
|
78
|
-
--foreground: 0.145 0 0;
|
|
79
|
-
/* Off-Black */
|
|
80
|
-
--card: 1 0 0;
|
|
81
|
-
--card-foreground: 0.145 0 0;
|
|
82
|
-
--popover: 1 0 0;
|
|
83
|
-
--popover-foreground: 0.145 0 0;
|
|
84
|
-
--primary: 0.205 0 0;
|
|
85
|
-
/* Deep Black/Gray */
|
|
86
|
-
--primary-foreground: 0.985 0 0;
|
|
87
|
-
--secondary: 0.97 0 0;
|
|
88
|
-
/* Very Light Gray */
|
|
89
|
-
--secondary-foreground: 0.205 0 0;
|
|
90
|
-
--muted: 0.97 0 0;
|
|
91
|
-
--muted-foreground: 0.556 0 0;
|
|
92
|
-
--accent: 0.97 0 0;
|
|
93
|
-
--accent-foreground: 0.205 0 0;
|
|
94
|
-
--destructive: 0.577 0.245 27.325;
|
|
95
|
-
--destructive-foreground: 0.985 0 0;
|
|
96
|
-
--border: 0.922 0 0;
|
|
97
|
-
--input: 0.922 0 0;
|
|
98
|
-
--ring: 0.205 0 0;
|
|
99
|
-
--radius: 0.5rem;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
.dark {
|
|
103
|
-
/* Dark Mode - OKLCH */
|
|
104
|
-
--background: 0.145 0 0;
|
|
105
|
-
/* Dark Gray/Black */
|
|
106
|
-
--foreground: 0.985 0 0;
|
|
107
|
-
/* Off-White */
|
|
108
|
-
--card: 0.145 0 0;
|
|
109
|
-
--card-foreground: 0.985 0 0;
|
|
110
|
-
--popover: 0.145 0 0;
|
|
111
|
-
--popover-foreground: 0.985 0 0;
|
|
112
|
-
--primary: 0.985 0 0;
|
|
113
|
-
/* Light Gray/White */
|
|
114
|
-
--primary-foreground: 0.145 0 0;
|
|
115
|
-
--secondary: 0.269 0 0;
|
|
116
|
-
--secondary-foreground: 0.985 0 0;
|
|
117
|
-
--muted: 0.269 0 0;
|
|
118
|
-
--muted-foreground: 0.708 0 0;
|
|
119
|
-
--accent: 0.269 0 0;
|
|
120
|
-
--accent-foreground: 0.985 0 0;
|
|
121
|
-
--destructive: 0.396 0.141 25.723;
|
|
122
|
-
--destructive-foreground: 0.985 0 0;
|
|
123
|
-
--border: 0.269 0 0;
|
|
124
|
-
--input: 0.269 0 0;
|
|
125
|
-
--ring: 0.869 0 0;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
@layer base {
|
|
130
|
-
* {
|
|
131
|
-
border-color: var(--color-border);
|
|
132
|
-
outline-color: var(--color-ring);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
body {
|
|
136
|
-
background-color: var(--color-background);
|
|
137
|
-
color: var(--color-foreground);
|
|
138
|
-
font-family: 'Inter', system-ui, sans-serif;
|
|
139
|
-
}
|
|
140
|
-
}
|
package/src/lib/api-config.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
// API Configuration for Hybrid Architecture
|
|
2
|
-
// - Edge Functions: Auth, OAuth, Database proxy
|
|
3
|
-
// - Express API: Email sync, AI processing (Local App)
|
|
4
|
-
|
|
5
|
-
import { getSupabaseConfig } from './supabase-config';
|
|
6
|
-
|
|
7
|
-
export interface ApiConfig {
|
|
8
|
-
edgeFunctionsUrl: string;
|
|
9
|
-
expressApiUrl: string;
|
|
10
|
-
anonKey: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function getApiConfig(): ApiConfig {
|
|
14
|
-
const supabaseConfig = getSupabaseConfig();
|
|
15
|
-
|
|
16
|
-
// Edge Functions URL: https://PROJECT_ID.supabase.co/functions/v1
|
|
17
|
-
const edgeFunctionsUrl = supabaseConfig
|
|
18
|
-
? `${supabaseConfig.url}/functions/v1`
|
|
19
|
-
: '';
|
|
20
|
-
|
|
21
|
-
const anonKey = supabaseConfig ? supabaseConfig.anonKey : '';
|
|
22
|
-
|
|
23
|
-
// Express API URL Discovery:
|
|
24
|
-
// 1. If we are in Vite Dev Mode (port 5173), use VITE_API_URL or default 3004
|
|
25
|
-
// 2. If we are running on the Unified Server (production/npx), use the current window origin
|
|
26
|
-
const isViteDev = window.location.port === '5173';
|
|
27
|
-
const envApiUrl = import.meta.env.VITE_API_URL;
|
|
28
|
-
|
|
29
|
-
let expressApiUrl = '';
|
|
30
|
-
if (isViteDev) {
|
|
31
|
-
expressApiUrl = envApiUrl || 'http://localhost:3004';
|
|
32
|
-
} else {
|
|
33
|
-
// Use current window origin (e.g. http://localhost:3008)
|
|
34
|
-
expressApiUrl = window.location.origin;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
edgeFunctionsUrl,
|
|
39
|
-
expressApiUrl,
|
|
40
|
-
anonKey,
|
|
41
|
-
};
|
|
42
|
-
}
|