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

fix: enhancer, feat: allow together ai models #1212

Open
wants to merge 9 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
42 changes: 39 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,34 @@ git clone https://github.com/stackblitz/bolt.new.git
pnpm install
```

3. Create a `.env.local` file in the root directory and add your Anthropic API key:
3. Set Up Supabase Auth & Providers:

- Create a new project on Supabase and generate a new anon key.
- Add the anon key to the `.env.local` file.
- Add the supabase url to the `.env.local` file.
- Configure supabase providers (Google, GitHub, etc).

4. Create a `.env.local` file in the root directory and add your Anthropic API key:

```
ANTHROPIC_API_KEY=your_api_key
TOGETHER_API_KEY=your_api_key
```

```
# SUPABASE
SUPABASE_URL=your_supabase_url
SUPABASE_ANON_KEY=your_anon_key
# Client Supabase
VITE_SUPABASE_URL=your_supabase_url
VITE_SUPABASE_ANON_KEY=your_anon_key
```
ANTHROPIC_API_KEY=XXX

```
# NETLIFY
NETLIFY_AUTH_TOKEN=your_auth_token
NETLIFY_CLIENT_SECRET=your_client_secret
VITE_NETLIFY_CLIENT_ID=your_client_id
```

Optionally, you can set the debug level:
Expand All @@ -70,9 +94,21 @@ VITE_LOG_LEVEL=debug

**Important**: Never commit your `.env.local` file to version control. It's already included in .gitignore.

## Add Custom Models from Together AI

To add custom models from Together AI, you can add them to the `app/components/chat/ProviderSelector.tsx` file.

```
const togetherModels = [
'meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo',
'meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo',
'mistralai/Mixtral-8x7B-Instruct-v0.1',
'... add more models here ...'
];

## Available Scripts

- `pnpm run dev`: Starts the development server.
- `pnpm run dev`: Starts the development server (use Chrome Canary for best results when testing locally).
- `pnpm run build`: Builds the project.
- `pnpm run start`: Runs the built application locally using Wrangler Pages. This script uses `bindings.sh` to set up necessary bindings so you don't have to duplicate environment variables.
- `pnpm run preview`: Builds the project and then starts it locally, useful for testing the production build. Note, HTTP streaming currently doesn't work as expected with `wrangler pages dev`.
Expand Down
225 changes: 225 additions & 0 deletions app/components/auth/Auth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
'use client'

import { useState, useEffect } from 'react'
import { useNavigate } from '@remix-run/react'
import { createClient } from '~/utils/supabase.client'
import type { SupabaseClient } from '@supabase/supabase-js'
import { motion, AnimatePresence } from 'framer-motion'
import { Github, Mail, Apple } from 'lucide-react'

interface AuthComponentProps {
onClose: () => void
}

export default function AuthComponent({ onClose }: AuthComponentProps = { onClose: () => {} }) {
const [supabase, setSupabase] = useState<SupabaseClient | null>(null)
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [activeTab, setActiveTab] = useState<'signin' | 'signup'>('signin')
const navigate = useNavigate()

useEffect(() => {
const client = createClient()
if (client) {
setSupabase(client)
}
}, [])

useEffect(() => {
if (!supabase) return

const {
data: { subscription },
} = supabase.auth.onAuthStateChange((event, session) => {
if (event === 'SIGNED_IN' && session) {
onClose()
navigate('/')
}
})

return () => subscription.unsubscribe()
}, [supabase, navigate, onClose])

const handleSignIn = async (e: React.FormEvent) => {
e.preventDefault()
if (!supabase) return

setLoading(true)
setError(null)

const { error } = await supabase.auth.signInWithPassword({ email, password })

if (error) {
setError(error.message)
}

setLoading(false)
}

const handleSignUp = async (e: React.FormEvent) => {
e.preventDefault()
if (!supabase) return

setLoading(true)
setError(null)

const { error } = await supabase.auth.signUp({ email, password })

if (error) {
setError(error.message)
} else {
setError('Please check your email for the confirmation link.')
}

setLoading(false)
}

const handleOAuthSignIn = async (provider: 'github' | 'google' | 'apple') => {
if (!supabase) return

setLoading(true)
setError(null)

try {
const { error } = await supabase.auth.signInWithOAuth({
provider,
options: {
redirectTo: `${window.location.origin}/auth/callback`,
},
})
if (error) {
setError(error.message)
}
} catch (err) {
setError('An unexpected error occurred')
} finally {
setLoading(false)
}
}

if (!supabase) {
return <div className="text-center text-gray-300">Loading...</div>
}

if (loading) {
return (
<div className="w-full min-w-md max-w-md p-8 bg-bolt-elements-background-depth-2 rounded-lg shadow-lg flex flex-col items-center justify-center gap-4">
<div className="i-svg-spinners:90-ring-with-bg w-8 h-8 text-bolt-elements-button-primary-background" />
<p className="text-bolt-elements-textPrimary text-lg">Signing in...</p>
</div>
);
}

return (
<div className="w-full min-w-md max-w-md p-8 bg-bolt-elements-background-depth-2 rounded-lg shadow-lg">
<div className="flex mb-6">
<button
className={`flex-1 py-2 text-lg font-semibold transition-colors duration-300 bg-transparent ${
activeTab === 'signin' ? 'text-bolt-elements-button-primary-background border-b-2 border-bolt-elements-button-primary-background' : 'text-gray-400'
}`}
onClick={() => setActiveTab('signin')}
>
Sign In
</button>
<button
className={`flex-1 py-2 text-lg font-semibold transition-colors duration-300 bg-transparent ${
activeTab === 'signup' ? 'text-bolt-elements-button-primary-background border-b-2 border-bolt-elements-button-primary-background' : 'text-gray-400'
}`}
onClick={() => setActiveTab('signup')}
>
Sign Up
</button>
</div>

<AnimatePresence mode="wait">
<motion.form
key={activeTab}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
onSubmit={activeTab === 'signin' ? handleSignIn : handleSignUp}
className="space-y-4"
>
<div>
<label htmlFor="email" className="block mb-1 font-medium text-gray-300">
Email
</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
className="w-full px-3 py-2 bg-bolt-elements-background-depth-3 text-white border border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-bolt-elements-button-primary-background"
required
/>
</div>
<div>
<label htmlFor="password" className="block mb-1 font-medium text-gray-300">
Password
</label>
<input
type="password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 bg-bolt-elements-background-depth-3 text-white border border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-bolt-elements-button-primary-background"
required
/>
</div>
<motion.button
type="submit"
className="w-full bg-bolt-elements-button-primary-background text-white py-2 rounded-md hover:bg-bolt-elements-button-primary-hover transition-colors duration-300"
disabled={loading}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
{loading ? 'Processing...' : activeTab === 'signin' ? 'Sign In' : 'Sign Up'}
</motion.button>
</motion.form>
</AnimatePresence>

{error && (
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="text-red-500 mt-4 text-center"
>
{error}
</motion.p>
)}

<div className="mt-8">
<p className="text-center mb-4 text-gray-400">Or continue with</p>
<div className="flex justify-center space-x-4">
<motion.button
onClick={() => handleOAuthSignIn('github')}
className="p-2 bg-bolt-elements-background-depth-3 rounded-full hover:bg-bolt-elements-button-primary-hover transition-colors duration-300"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
>
<Github className="w-6 h-6 text-white" />
</motion.button>
<motion.button
onClick={() => handleOAuthSignIn('google')}
className="p-2 bg-bolt-elements-background-depth-3 rounded-full hover:bg-bolt-elements-button-primary-hover transition-colors duration-300"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
>
<Mail className="w-6 h-6 text-white" />
</motion.button>
<motion.button
onClick={() => handleOAuthSignIn('apple')}
className="p-2 bg-bolt-elements-background-depth-3 rounded-full hover:bg-bolt-elements-button-primary-hover transition-colors duration-300"
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
>
<Apple className="w-6 h-6 text-white" />
</motion.button>
</div>
</div>
</div>
)
}
16 changes: 13 additions & 3 deletions app/components/chat/Artifact.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const Artifact = memo(({ messageId }: ArtifactProps) => {
>
<div className="px-5 p-3.5 w-full text-left">
<div className="w-full text-bolt-elements-textPrimary font-medium leading-5 text-sm">{artifact?.title}</div>
<div className="w-full w-full text-bolt-elements-textSecondary text-xs mt-0.5">Click to open Workbench</div>
<div className="w-full text-bolt-elements-textSecondary text-xs mt-0.5">Click to open Workbench</div>
</div>
</button>
<div className="bg-bolt-elements-artifacts-borderColor w-[1px]" />
Expand Down Expand Up @@ -151,7 +151,13 @@ const ActionList = memo(({ actions }: ActionListProps) => {
<div className="flex items-center gap-1.5 text-sm">
<div className={classNames('text-lg', getIconColor(action.status))}>
{status === 'running' ? (
<div className="i-svg-spinners:90-ring-with-bg"></div>
<>
{type !== 'start' ? (
<div className="i-svg-spinners:90-ring-with-bg"></div>
) : (
<div className="i-ph:terminal text-[#2ba6ff]"></div>
)}
</>
) : status === 'pending' ? (
<div className="i-ph:circle-duotone"></div>
) : status === 'complete' ? (
Expand All @@ -171,9 +177,13 @@ const ActionList = memo(({ actions }: ActionListProps) => {
<div className="flex items-center w-full min-h-[28px]">
<span className="flex-1">Run command</span>
</div>
) : type === 'start' ? (
<div className="flex items-center w-full min-h-[28px]">
<span className="flex-1">Start application</span>
</div>
) : null}
</div>
{type === 'shell' && (
{(type === 'shell' || type === 'start') && (
<ShellCodeBlock
classsName={classNames('mt-1', {
'mb-3.5': !isLast,
Expand Down
Loading