Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wip] supabase #35

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 21 additions & 17 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,53 @@
# Get your GROQ API Key here -
# https://console.groq.com/keys
# You only need this environment variable set if you want to use Groq models
GROQ_API_KEY=
GROQ_API_KEY=your_groq_api_key

# Get your HuggingFace API Key here -
# https://huggingface.co/settings/tokens
# You only need this environment variable set if you want to use HuggingFace models
HuggingFace_API_KEY=
HuggingFace_API_KEY=your_huggingface_api_key


# Get your Open AI API Key by following these instructions -
# https://help.openai.com/en/articles/4936850-where-do-i-find-my-openai-api-key
# You only need this environment variable set if you want to use GPT models
OPENAI_API_KEY=
OPENAI_API_KEY=your_openai_api_key

# Get your Anthropic API Key in your account settings -
# https://console.anthropic.com/settings/keys
# You only need this environment variable set if you want to use Claude models
ANTHROPIC_API_KEY=
ANTHROPIC_API_KEY=your_anthropic_api_key

# Get your OpenRouter API Key in your account settings -
# https://openrouter.ai/settings/keys
# You only need this environment variable set if you want to use OpenRouter models
OPEN_ROUTER_API_KEY=
OPEN_ROUTER_API_KEY=your_openrouter_api_key

# Get your Google Generative AI API Key by following these instructions -
# https://console.cloud.google.com/apis/credentials
# You only need this environment variable set if you want to use Google Generative AI models
GOOGLE_GENERATIVE_AI_API_KEY=
GOOGLE_GENERATIVE_AI_API_KEY=your_google_api_key

# You only need this environment variable set if you want to use oLLAMA models
# DONT USE http://localhost:11434 due to IPV6 issues
# USE EXAMPLE http://127.0.0.1:11434
OLLAMA_API_BASE_URL=
OLLAMA_API_BASE_URL=your_ollama_base_url

# You only need this environment variable set if you want to use OpenAI Like models
OPENAI_LIKE_API_BASE_URL=
OPENAI_LIKE_API_BASE_URL=your_openai_like_base_url

# You only need this environment variable set if you want to use Together AI models
TOGETHER_API_BASE_URL=
TOGETHER_API_BASE_URL=your_together_base_url

# You only need this environment variable set if you want to use DeepSeek models through their API
DEEPSEEK_API_KEY=
DEEPSEEK_API_KEY=your_deepseek_api_key

# Get your OpenAI Like API Key
OPENAI_LIKE_API_KEY=
OPENAI_LIKE_API_KEY=your_openai_like_api_key

# Get your Together API Key
TOGETHER_API_KEY=
TOGETHER_API_KEY=your_together_api_key

# You only need this environment variable set if you want to use Hyperbolic models
#Get your Hyperbolics API Key at https://app.hyperbolic.xyz/settings
Expand All @@ -60,7 +60,7 @@ HYPERBOLIC_API_BASE_URL=
# Get your Mistral API Key by following these instructions -
# https://console.mistral.ai/api-keys/
# You only need this environment variable set if you want to use Mistral models
MISTRAL_API_KEY=
MISTRAL_API_KEY=your_mistral_api_key

# Get the Cohere Api key by following these instructions -
# https://dashboard.cohere.com/api-keys
Expand All @@ -71,17 +71,17 @@ COHERE_API_KEY=
# Make sure to enable CORS
# DONT USE http://localhost:1234 due to IPV6 issues
# Example: http://127.0.0.1:1234
LMSTUDIO_API_BASE_URL=
LMSTUDIO_API_BASE_URL=your_lmstudio_base_url

# Get your xAI API key
# https://x.ai/api
# You only need this environment variable set if you want to use xAI models
XAI_API_KEY=
XAI_API_KEY=your_xai_api_key

# Get your Perplexity API Key here -
# https://www.perplexity.ai/settings/api
# You only need this environment variable set if you want to use Perplexity models
PERPLEXITY_API_KEY=
PERPLEXITY_API_KEY=your_perplexity_api_key

# Get your AWS configuration
# https://console.aws.amazon.com/iam/home
Expand All @@ -92,7 +92,7 @@ PERPLEXITY_API_KEY=
# - sessionToken (optional): Temporary session token if using an IAM role or temporary credentials.
# Example JSON:
# {"region": "us-east-1", "accessKeyId": "yourAccessKeyId", "secretAccessKey": "yourSecretAccessKey", "sessionToken": "yourSessionToken"}
AWS_BEDROCK_CONFIG=
AWS_BEDROCK_CONFIG=your_aws_bedrock_config

# Include this environment variable if you want more logging for debugging locally
VITE_LOG_LEVEL=debug
Expand All @@ -108,3 +108,7 @@ DEFAULT_NUM_CTX=
# set these to enable opentelemetry
HONEYCOMB_API_KEY=
HONEYCOMB_DATASET=

# Supabase Configuration
SUPABASE_URL=your_supabase_project_url
SUPABASE_ANON_KEY=your_supabase_anon_key
3 changes: 3 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["denoland.vscode-deno"]
}
89 changes: 89 additions & 0 deletions app/components/auth/Auth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { useState } from 'react';
import { toast } from 'react-toastify';
import { signInWithEmail, signUp } from '~/lib/stores/auth';

interface AuthProps {
onClose: () => void;
}

export function Auth({ onClose }: AuthProps) {
const [isSignUp, setIsSignUp] = useState(false);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [loading, setLoading] = useState(false);

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);

try {
if (isSignUp) {
await signUp(email, password);
toast.success('Check your email for the confirmation link');
} else {
await signInWithEmail(email, password);
toast.success('Signed in successfully');
onClose();
}
} catch (error) {
toast.error(error instanceof Error ? error.message : 'Authentication failed');
} finally {
setLoading(false);
}
};

return (
<div className="p-6 bg-bolt-elements-background-depth-1 rounded-lg shadow-lg max-w-md w-full">
<h2 className="text-2xl font-bold mb-6 text-center">
{isSignUp ? 'Create Account' : 'Sign In'}
</h2>

<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="email" className="block text-sm font-medium mb-1">
Email
</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full p-2 border rounded bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary"
required
/>
</div>

<div>
<label htmlFor="password" className="block text-sm font-medium mb-1">
Password
</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full p-2 border rounded bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary"
required
/>
</div>

<button
type="submit"
disabled={loading}
className="w-full py-2 px-4 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:opacity-50"
>
{loading ? 'Loading...' : isSignUp ? 'Sign Up' : 'Sign In'}
</button>
</form>

<div className="mt-4 text-center">
<button
onClick={() => setIsSignUp(!isSignUp)}
className="text-blue-500 hover:underline"
>
{isSignUp ? 'Already have an account? Sign in' : "Don't have an account? Sign up"}
</button>
</div>
</div>
);
}
20 changes: 20 additions & 0 deletions app/components/auth/AuthModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import ReactModal from 'react-modal';
import { Auth } from './Auth';

interface AuthModalProps {
isOpen: boolean;
onClose: () => void;
}

export function AuthModal({ isOpen, onClose }: AuthModalProps) {
return (
<ReactModal
isOpen={isOpen}
onRequestClose={onClose}
className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 outline-none"
overlayClassName="fixed inset-0 bg-black bg-opacity-50 z-50"
>
<Auth onClose={onClose} />
</ReactModal>
);
}
67 changes: 52 additions & 15 deletions app/components/chat/ImportFolderButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,42 @@ import React, { useState } from 'react';
import type { Message } from 'ai';
import { toast } from 'react-toastify';
import { MAX_FILES, isBinaryFile, shouldIncludeFile } from '~/utils/fileUtils';
import { createChatFromFolder, getFileArtifacts } from '~/utils/folderImport';
import { logStore } from '~/lib/stores/logs'; // Assuming logStore is imported from this location
import { logStore } from '~/lib/stores/logs';

interface ImportFolderButtonProps {
className?: string;
importChat?: (description: string, messages: Message[]) => Promise<void>;
}

interface FileArtifact {
path: string;
content: string;
}

interface ImportFolderResponse {
files: FileArtifact[];
}

interface CreateChatResponse {
messages: Message[];
}

export const ImportFolderButton: React.FC<ImportFolderButtonProps> = ({ className, importChat }) => {
const [isLoading, setIsLoading] = useState(false);

const handleFileChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const allFiles = Array.from(e.target.files || []);

const folderName = allFiles[0]?.webkitRelativePath.split('/')[0] || 'Unknown Folder';

const filteredFiles = allFiles.filter((file) => {
const path = file.webkitRelativePath.split('/').slice(1).join('/');
const include = shouldIncludeFile(path);

return include;
return shouldIncludeFile(path);
});

if (filteredFiles.length === 0) {
const error = new Error('No valid files found');
logStore.logError('File import failed - no valid files', error, { folderName: 'Unknown Folder' });
logStore.logError('File import failed - no valid files', error, { folderName });
toast.error('No files found in the selected folder');

return;
}

Expand All @@ -40,13 +50,10 @@ export const ImportFolderButton: React.FC<ImportFolderButtonProps> = ({ classNam
toast.error(
`This folder contains ${filteredFiles.length.toLocaleString()} files. This product is not yet optimized for very large projects. Please select a folder with fewer than ${MAX_FILES.toLocaleString()} files.`,
);

return;
}

const folderName = filteredFiles[0]?.webkitRelativePath.split('/')[0] || 'Unknown Folder';
setIsLoading(true);

const loadingToast = toast.loading(`Importing ${folderName}...`);

try {
Expand All @@ -66,7 +73,6 @@ export const ImportFolderButton: React.FC<ImportFolderButtonProps> = ({ classNam
const error = new Error('No text files found');
logStore.logError('File import failed - no text files', error, { folderName });
toast.error('No text files found in the selected folder');

return;
}

Expand All @@ -78,11 +84,42 @@ export const ImportFolderButton: React.FC<ImportFolderButtonProps> = ({ classNam
toast.info(`Skipping ${binaryFilePaths.length} binary files`);
}

const textFileArtifacts = await getFileArtifacts(textFiles);
const messages = await createChatFromFolder(textFileArtifacts, binaryFilePaths, folderName);
// First, upload the files
const formData = new FormData();
textFiles.forEach((file) => formData.append('files', file));

const uploadResponse = await fetch('/api/import-folder', {
method: 'POST',
body: formData,
});

if (!uploadResponse.ok) {
throw new Error('Failed to upload files');
}

const { files: fileArtifacts } = await uploadResponse.json() as ImportFolderResponse;

// Then, create the chat
const chatResponse = await fetch('/api/create-chat-from-folder', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
files: fileArtifacts,
binaryFiles: binaryFilePaths,
folderName,
}),
});

if (!chatResponse.ok) {
throw new Error('Failed to create chat');
}

const { messages } = await chatResponse.json() as CreateChatResponse;

if (importChat) {
await importChat(folderName, [...messages]);
await importChat(folderName, messages);
}

logStore.logSystem('Folder imported successfully', {
Expand Down
Loading