diff --git a/app/components/chat/Chat.client.tsx b/app/components/chat/Chat.client.tsx
index af5f2f44..4cf8c8ca 100644
--- a/app/components/chat/Chat.client.tsx
+++ b/app/components/chat/Chat.client.tsx
@@ -169,6 +169,10 @@ export const ChatImpl = memo(
supabase: {
isConnected: supabaseConn.isConnected,
hasSelectedProject: !!selectedProject,
+ credentials: {
+ supabaseUrl: supabaseConn?.credentials?.supabaseUrl,
+ anonKey: supabaseConn?.credentials?.anonKey,
+ },
},
},
sendExtraMessageFields: true,
diff --git a/app/components/chat/SupabaseConnection.tsx b/app/components/chat/SupabaseConnection.tsx
index 2b03f442..6b2e34a0 100644
--- a/app/components/chat/SupabaseConnection.tsx
+++ b/app/components/chat/SupabaseConnection.tsx
@@ -21,11 +21,11 @@ export function SupabaseConnection() {
handleCreateProject,
updateToken,
isConnected,
+ fetchProjectApiKeys,
} = useSupabaseConnection();
const currentChatId = useStore(chatId);
- // Add event listener for opening the connection dialog
useEffect(() => {
const handleOpenConnectionDialog = () => {
setIsDialogOpen(true);
@@ -38,7 +38,6 @@ export function SupabaseConnection() {
};
}, [setIsDialogOpen]);
- // Load the selected project from localStorage when connected or chat changes
useEffect(() => {
if (isConnected && currentChatId) {
const savedProjectId = localStorage.getItem(`supabase-project-${currentChatId}`);
@@ -70,6 +69,12 @@ export function SupabaseConnection() {
}
}, [isConnected, supabaseConn.token]);
+ useEffect(() => {
+ if (isConnected && supabaseConn.selectedProjectId && supabaseConn.token) {
+ fetchProjectApiKeys(supabaseConn.selectedProjectId).catch(console.error);
+ }
+ }, [isConnected, supabaseConn.selectedProjectId, supabaseConn.token]);
+
return (
diff --git a/app/lib/.server/llm/stream-text.ts b/app/lib/.server/llm/stream-text.ts
index 1958dfad..7f5b4857 100644
--- a/app/lib/.server/llm/stream-text.ts
+++ b/app/lib/.server/llm/stream-text.ts
@@ -16,6 +16,10 @@ export interface StreamingOptions extends Omit
[0]
supabaseConnection?: {
isConnected: boolean;
hasSelectedProject: boolean;
+ credentials?: {
+ anonKey?: string;
+ supabaseUrl?: string;
+ };
};
}
@@ -105,6 +109,7 @@ export async function streamText(props: {
supabase: {
isConnected: options?.supabaseConnection?.isConnected || false,
hasSelectedProject: options?.supabaseConnection?.hasSelectedProject || false,
+ credentials: options?.supabaseConnection?.credentials || undefined,
},
}) ?? getSystemPrompt();
diff --git a/app/lib/common/prompt-library.ts b/app/lib/common/prompt-library.ts
index 084925b7..f4747d71 100644
--- a/app/lib/common/prompt-library.ts
+++ b/app/lib/common/prompt-library.ts
@@ -8,6 +8,10 @@ export interface PromptOptions {
supabase?: {
isConnected: boolean;
hasSelectedProject: boolean;
+ credentials?: {
+ anonKey?: string;
+ supabaseUrl?: string;
+ };
};
}
diff --git a/app/lib/common/prompts/prompts.ts b/app/lib/common/prompts/prompts.ts
index 149f1b5f..eb7ec1a1 100644
--- a/app/lib/common/prompts/prompts.ts
+++ b/app/lib/common/prompts/prompts.ts
@@ -4,7 +4,11 @@ import { stripIndents } from '~/utils/stripIndent';
export const getSystemPrompt = (
cwd: string = WORK_DIR,
- supabase?: { isConnected: boolean; hasSelectedProject: boolean },
+ supabase?: {
+ isConnected: boolean;
+ hasSelectedProject: boolean;
+ credentials?: { anonKey?: string; supabaseUrl?: string };
+ },
) => `
You are Bolt, an expert AI assistant and exceptional senior software developer with vast knowledge across multiple programming languages, frameworks, and best practices.
@@ -76,8 +80,16 @@ You are Bolt, an expert AI assistant and exceptional senior software developer w
: ''
: ''
}
- The environment variables for Supabase connection will be available in the project's \`.env\` file.
- IMPORTANT: Create a .env file if it doesnt exist.
+ IMPORTANT: Create a .env file if it doesnt exist and include the following variables:
+ ${
+ supabase?.isConnected &&
+ supabase?.hasSelectedProject &&
+ supabase?.credentials?.supabaseUrl &&
+ supabase?.credentials?.anonKey
+ ? `VITE_SUPABASE_URL=${supabase.credentials.supabaseUrl}
+ VITE_SUPABASE_ANON_KEY=${supabase.credentials.anonKey}`
+ : 'SUPABASE_URL=your_supabase_url\nSUPABASE_ANON_KEY=your_supabase_anon_key'
+ }
NEVER modify any Supabase configuration or \`.env\` files.
CRITICAL DATA PRESERVATION AND SAFETY REQUIREMENTS:
diff --git a/app/lib/hooks/useSupabaseConnection.ts b/app/lib/hooks/useSupabaseConnection.ts
index 5bbe12ea..8a2f8118 100644
--- a/app/lib/hooks/useSupabaseConnection.ts
+++ b/app/lib/hooks/useSupabaseConnection.ts
@@ -2,21 +2,39 @@ import { useEffect, useState } from 'react';
import { toast } from 'react-toastify';
import { useStore } from '@nanostores/react';
import { logStore } from '~/lib/stores/logs';
-import { supabaseConnection, isConnecting, isFetchingStats, updateSupabaseConnection } from '~/lib/stores/supabase';
+import {
+ supabaseConnection,
+ isConnecting,
+ isFetchingStats,
+ isFetchingApiKeys,
+ updateSupabaseConnection,
+ fetchProjectApiKeys,
+} from '~/lib/stores/supabase';
export function useSupabaseConnection() {
const connection = useStore(supabaseConnection);
const connecting = useStore(isConnecting);
const fetchingStats = useStore(isFetchingStats);
+ const fetchingApiKeys = useStore(isFetchingApiKeys);
const [isProjectsExpanded, setIsProjectsExpanded] = useState(false);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
useEffect(() => {
const savedConnection = localStorage.getItem('supabase_connection');
+ const savedCredentials = localStorage.getItem('supabaseCredentials');
if (savedConnection) {
const parsed = JSON.parse(savedConnection);
+
+ if (savedCredentials && !parsed.credentials) {
+ parsed.credentials = JSON.parse(savedCredentials);
+ }
+
updateSupabaseConnection(parsed);
+
+ if (parsed.token && parsed.selectedProjectId && !parsed.credentials) {
+ fetchProjectApiKeys(parsed.selectedProjectId, parsed.token).catch(console.error);
+ }
}
}, []);
@@ -50,7 +68,6 @@ export function useSupabaseConnection() {
toast.success('Successfully connected to Supabase');
- // Keep the dialog open and expand the projects section
setIsProjectsExpanded(true);
return true;
@@ -72,7 +89,7 @@ export function useSupabaseConnection() {
setIsDropdownOpen(false);
};
- const selectProject = (projectId: string) => {
+ const selectProject = async (projectId: string) => {
const currentState = supabaseConnection.get();
let projectData = undefined;
@@ -85,7 +102,18 @@ export function useSupabaseConnection() {
project: projectData,
});
- toast.success('Project selected successfully');
+ if (projectId && currentState.token) {
+ try {
+ await fetchProjectApiKeys(projectId, currentState.token);
+ toast.success('Project selected successfully');
+ } catch (error) {
+ console.error('Failed to fetch API keys:', error);
+ toast.error('Selected project but failed to fetch API keys');
+ }
+ } else {
+ toast.success('Project selected successfully');
+ }
+
setIsDropdownOpen(false);
};
@@ -97,6 +125,7 @@ export function useSupabaseConnection() {
connection,
connecting,
fetchingStats,
+ fetchingApiKeys,
isProjectsExpanded,
setIsProjectsExpanded,
isDropdownOpen,
@@ -107,5 +136,12 @@ export function useSupabaseConnection() {
handleCreateProject,
updateToken: (token: string) => updateSupabaseConnection({ ...connection, token }),
isConnected: !!(connection.user && connection.token),
+ fetchProjectApiKeys: (projectId: string) => {
+ if (connection.token) {
+ return fetchProjectApiKeys(projectId, connection.token);
+ }
+
+ return Promise.reject(new Error('No token available'));
+ },
};
}
diff --git a/app/lib/stores/supabase.ts b/app/lib/stores/supabase.ts
index 9f624e5b..3b8a5813 100644
--- a/app/lib/stores/supabase.ts
+++ b/app/lib/stores/supabase.ts
@@ -1,5 +1,5 @@
import { atom } from 'nanostores';
-import type { SupabaseUser, SupabaseStats } from '~/types/supabase';
+import type { SupabaseUser, SupabaseStats, SupabaseApiKey, SupabaseCredentials } from '~/types/supabase';
export interface SupabaseProject {
id: string;
@@ -22,10 +22,10 @@ export interface SupabaseConnectionState {
stats?: SupabaseStats;
selectedProjectId?: string;
isConnected?: boolean;
- project?: SupabaseProject; // Add the selected project data
+ project?: SupabaseProject;
+ credentials?: SupabaseCredentials;
}
-// Init from localStorage if available
const savedConnection = typeof localStorage !== 'undefined' ? localStorage.getItem('supabase_connection') : null;
const initialState: SupabaseConnectionState = savedConnection
@@ -36,30 +36,28 @@ const initialState: SupabaseConnectionState = savedConnection
stats: undefined,
selectedProjectId: undefined,
isConnected: false,
- project: undefined, // Initialize as undefined
+ project: undefined,
};
export const supabaseConnection = atom(initialState);
-// After init, fetch stats if we have a token
if (initialState.token && !initialState.stats) {
fetchSupabaseStats(initialState.token).catch(console.error);
}
export const isConnecting = atom(false);
export const isFetchingStats = atom(false);
+export const isFetchingApiKeys = atom(false);
export function updateSupabaseConnection(connection: Partial) {
const currentState = supabaseConnection.get();
- // Set isConnected based on user presence AND token
if (connection.user !== undefined || connection.token !== undefined) {
const newUser = connection.user !== undefined ? connection.user : currentState.user;
const newToken = connection.token !== undefined ? connection.token : currentState.token;
connection.isConnected = !!(newUser && newToken);
}
- // Update the project data when selectedProjectId changes
if (connection.selectedProjectId !== undefined) {
if (connection.selectedProjectId && currentState.stats?.projects) {
const selectedProject = currentState.stats.projects.find(
@@ -69,7 +67,6 @@ export function updateSupabaseConnection(connection: Partial key.name === 'anon' || key.name === 'public');
+
+ if (anonKey) {
+ const supabaseUrl = `https://${projectId}.supabase.co`;
+
+ updateSupabaseConnection({
+ credentials: {
+ anonKey: anonKey.api_key,
+ supabaseUrl,
+ },
+ });
+
+ return { anonKey: anonKey.api_key, supabaseUrl };
+ }
+
+ return null;
+ } catch (error) {
+ console.error('Failed to fetch project API keys:', error);
+ throw error;
+ } finally {
+ isFetchingApiKeys.set(false);
+ }
+}
diff --git a/app/routes/api.chat.ts b/app/routes/api.chat.ts
index 6861d15d..5917dfc4 100644
--- a/app/routes/api.chat.ts
+++ b/app/routes/api.chat.ts
@@ -45,6 +45,10 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
supabase?: {
isConnected: boolean;
hasSelectedProject: boolean;
+ credentials?: {
+ anonKey?: string;
+ supabaseUrl?: string;
+ };
};
}>();
@@ -183,7 +187,6 @@ async function chatAction({ context, request }: ActionFunctionArgs) {
// logger.debug('Code Files Selected');
}
- // Stream the text
const options: StreamingOptions = {
supabaseConnection: supabase,
toolChoice: 'none',
diff --git a/app/routes/api.supabase.ts b/app/routes/api.supabase.ts
index 56d9f810..955ae44e 100644
--- a/app/routes/api.supabase.ts
+++ b/app/routes/api.supabase.ts
@@ -7,7 +7,6 @@ export const action: ActionFunction = async ({ request }) => {
return json({ error: 'Method not allowed' }, { status: 405 });
}
- // Inside the action function
try {
const { token } = (await request.json()) as any;
@@ -27,17 +26,14 @@ export const action: ActionFunction = async ({ request }) => {
const projects = (await projectsResponse.json()) as SupabaseProject[];
- // Create a Map to store unique projects by ID
const uniqueProjectsMap = new Map();
- // Only keep the latest version of each project
for (const project of projects) {
if (!uniqueProjectsMap.has(project.id)) {
uniqueProjectsMap.set(project.id, project);
}
}
- // Debug log to see unique projects
console.log(
'Unique projects:',
Array.from(uniqueProjectsMap.values()).map((p) => ({ id: p.id, name: p.name })),
@@ -45,7 +41,6 @@ export const action: ActionFunction = async ({ request }) => {
const uniqueProjects = Array.from(uniqueProjectsMap.values());
- // Sort projects by creation date (newest first)
uniqueProjects.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
return json({
diff --git a/app/routes/api.supabase.variables.ts b/app/routes/api.supabase.variables.ts
new file mode 100644
index 00000000..fd2d028f
--- /dev/null
+++ b/app/routes/api.supabase.variables.ts
@@ -0,0 +1,33 @@
+import { json } from '@remix-run/node';
+import type { ActionFunctionArgs } from '@remix-run/node';
+
+export async function action({ request }: ActionFunctionArgs) {
+ try {
+ // Add proper type assertion for the request body
+ const body = (await request.json()) as { projectId?: string; token?: string };
+ const { projectId, token } = body;
+
+ if (!projectId || !token) {
+ return json({ error: 'Project ID and token are required' }, { status: 400 });
+ }
+
+ const response = await fetch(`https://api.supabase.com/v1/projects/${projectId}/api-keys`, {
+ method: 'GET',
+ headers: {
+ Authorization: `Bearer ${token}`,
+ 'Content-Type': 'application/json',
+ },
+ });
+
+ if (!response.ok) {
+ return json({ error: `Failed to fetch API keys: ${response.statusText}` }, { status: response.status });
+ }
+
+ const apiKeys = await response.json();
+
+ return json({ apiKeys });
+ } catch (error) {
+ console.error('Error fetching project API keys:', error);
+ return json({ error: error instanceof Error ? error.message : 'Unknown error occurred' }, { status: 500 });
+ }
+}
diff --git a/app/types/supabase.ts b/app/types/supabase.ts
index d16d50c2..f99bbaf2 100644
--- a/app/types/supabase.ts
+++ b/app/types/supabase.ts
@@ -19,3 +19,13 @@ export interface SupabaseStats {
projects: SupabaseProject[];
totalProjects: number;
}
+
+export interface SupabaseApiKey {
+ name: string;
+ api_key: string;
+}
+
+export interface SupabaseCredentials {
+ anonKey?: string;
+ supabaseUrl?: string;
+}