Skip to content

Commit

Permalink
Merge pull request #2 from arsenikstiger/local-configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
arsenikstiger authored Oct 23, 2024
2 parents 867fb2b + c110932 commit d98264d
Show file tree
Hide file tree
Showing 12 changed files with 354 additions and 68 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

# Bolt.new Fork by Cole Medin

[Lire en français](README_FR.md)

This fork of bolt.new allows you to choose the LLM that you use for each prompt! Currently you can use OpenAI, Anthropic, Ollama, OpenRouter, Gemini, or Groq models - and it is easily extended to use any other model supported by the Vercel AI SDK! See instructions below for running this locally and extending to include more models.

# Requested Additions to this Fork - Feel Free to Contribute!!
Expand Down Expand Up @@ -39,13 +41,14 @@ Bolt.new is an AI-powered web development agent that allows you to prompt, run,
Claude, v0, etc are incredible- but you can't install packages, run backends or edit code. That’s where Bolt.new stands out:

- **Full-Stack in the Browser**: Bolt.new integrates cutting-edge AI models with an in-browser development environment powered by **StackBlitz’s WebContainers**. This allows you to:

- Install and run npm tools and libraries (like Vite, Next.js, and more)
- Run Node.js servers
- Interact with third-party APIs
- Deploy to production from chat
- Share your work via a URL

- **AI with Environment Control**: Unlike traditional dev environments where the AI can only assist in code generation, Bolt.new gives AI models **complete control** over the entire environment including the filesystem, node server, package manager, terminal, and browser console. This empowers AI agents to handle the entire app lifecycle—from creation to deployment.
- **AI with Environment Control**: Unlike traditional dev environments where the AI can only assist in code generation, Bolt.new gives AI models **complete control** over the entire environment including the filesystem, node server, package manager, terminal, and browser console. This empowers AI agents to handle the entire app lifecycle—from creation to deployment.

Whether you’re an experienced developer, a PM or designer, Bolt.new allows you to build production-grade full-stack applications with ease.

Expand Down Expand Up @@ -90,7 +93,7 @@ VITE_LOG_LEVEL=debug

## Adding New LLMs:

To make new LLMs available to use in this version of Bolt.new, head on over to `app/utils/constants.ts` and find the constant MODEL_LIST. Each element in this array is an object that has the model ID for the name (get this from the provider's API documentation), a lable for the frontend model dropdown, and the provider.
To make new LLMs available to use in this version of Bolt.new, head on over to `app/utils/constants.ts` and find the constant MODEL_LIST. Each element in this array is an object that has the model ID for the name (get this from the provider's API documentation), a lable for the frontend model dropdown, and the provider.

By default, Anthropic, OpenAI, Groq, and Ollama are implemented as providers, but the YouTube video for this repo covers how to extend this to work with more providers if you wish!

Expand Down
133 changes: 133 additions & 0 deletions README_FR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Bolt.new: Développement Web Full-Stack Powered by AI dans le Navigateur

![Bolt.new: Développement Web Full-Stack Powered by AI dans le Navigateur](./public/social_preview_index.jpg)

# Bolt.new Fork par Cole Medin

Ce fork de bolt.new vous permet de choisir le LLM que vous utilisez pour chaque prompt ! Actuellement, vous pouvez utiliser les modèles OpenAI, Anthropic, Ollama, OpenRouter, Gemini, ou Groq - et il est facilement extensible pour utiliser tout autre modèle supporté par le Vercel AI SDK ! Consultez les instructions ci-dessous pour exécuter ceci localement et étendre pour inclure plus de modèles.

# Additions Requises pour ce Fork - N'hésitez pas à Contribuer !!

- ✅ Intégration OpenRouter (@coleam00)
- ✅ Intégration Gemini (@jonathands)
- ✅ Autogénérer les modèles Ollama à partir de ce qui est téléchargé (@mosquet)
- ✅ Filtrer les modèles par fournisseur (@jasonm23)
- ✅ Télécharger le projet en tant que ZIP (@fabwaseem)
- ⬜ Intégration LM Studio
- ⬜ Intégration DeepSeek API
- ⬜ Intégration Together
- ⬜ Intégration Azure Open AI API
- ⬜ Intégration HuggingFace
- ⬜ Intégration Perplexity
- ⬜ Conteneuriser l'application avec Docker pour une installation facile
- ⬜ Meilleure incitation pour les LLMs plus petits (la fenêtre de code ne démarre parfois pas)
- ⬜ Joindre des images aux prompts
- ⬜ Exécuter des agents dans le backend au lieu d'un seul appel de modèle
- ⬜ Publier des projets directement sur GitHub
- ⬜ Déployer directement sur Vercel/Netlify/autres plateformes similaires
- ⬜ Charger des projets locaux dans l'application
- ⬜ Améliorations de la prompt principale de Bolt.new dans `app\lib\.server\llm\prompts.ts` (il y a définitivement une opportunité là)
- ⬜ Capacité à revenir au code à une version antérieure
- ⬜ Mise en cache des prompts
- ⬜ Capacité à entrer des clés API dans l'interface utilisateur
- ⬜ Empêcher Bolt de réécrire les fichiers aussi souvent

# Bolt.new: Développement Web Full-Stack Powered by AI dans le Navigateur

Bolt.new est un agent de développement web powered by AI qui vous permet de prompter, exécuter, éditer et déployer des applications full-stack directement depuis votre navigateur—aucune configuration locale requise. Si vous êtes ici pour construire votre propre agent de développement web powered by AI en utilisant le code source open source de Bolt, [cliquez ici pour commencer!](./CONTRIBUTING.md)

## Ce qui Rend Bolt.new Différent

Claude, v0, etc. sont incroyables - mais vous ne pouvez pas installer de packages, exécuter des backends ou éditer du code. C'est là que Bolt.new se distingue :

- **Full-Stack dans le Navigateur** : Bolt.new intègre des modèles AI de pointe avec un environnement de développement dans le navigateur powered by **StackBlitz’s WebContainers**. Cela vous permet de :

- Installer et exécuter des outils et bibliothèques npm (comme Vite, Next.js, et plus)
- Exécuter des serveurs Node.js
- Interagir avec des API tierces
- Déployer en production depuis le chat
- Partager votre travail via une URL

- **AI avec Contrôle de l'Environnement** : Contrairement aux environnements de développement traditionnels où l'AI ne peut qu'aider à la génération de code, Bolt.new donne aux modèles AI un **contrôle complet** sur tout l'environnement, y compris le système de fichiers, le serveur node, le gestionnaire de packages, le terminal, et la console du navigateur. Cela permet aux agents AI de gérer tout le cycle de vie de l'application—de la création au déploiement.

Que vous soyez un développeur expérimenté, un chef de projet ou un designer, Bolt.new vous permet de construire des applications full-stack de qualité production avec facilité.

Pour les développeurs intéressés par la construction de leurs propres outils de développement powered by AI avec WebContainers, consultez le code source open source de Bolt dans ce dépôt !

## Prérequis

Avant de commencer, assurez-vous d'avoir installé les éléments suivants :

- Node.js (v20.15.1)
- pnpm (v9.4.0)

## Configuration

1. Clonez le dépôt (si ce n'est pas déjà fait) :

```bash
git clone https://github.com/coleam00/bolt.new-any-llm.git
```

2. Installez les dépendances :

```bash
pnpm install
```

3. Renommez `.env.example` en .env.local et ajoutez vos clés API LLM (vous n'avez qu'à définir celles que vous souhaitez utiliser et Ollama n'a pas besoin d'une clé API car il s'exécute localement sur votre ordinateur) :

```
GROQ_API_KEY=XXX
OPENAI_API_KEY=XXX
ANTHROPIC_API_KEY=XXX
```

Optionnellement, vous pouvez définir le niveau de débogage :

```
VITE_LOG_LEVEL=debug
```

**Important** : Ne commettez jamais votre fichier `.env.local` dans le contrôle de version. Il est déjà inclus dans .gitignore.

## Ajout de Nouveaux LLMs :

Pour rendre de nouveaux LLMs disponibles à l'utilisation dans cette version de Bolt.new, rendez-vous sur `app/utils/constants.ts` et trouvez la constante MODEL_LIST. Chaque élément de ce tableau est un objet qui a l'ID du modèle pour le nom (obtenez ceci à partir de la documentation de l'API du fournisseur), une étiquette pour le menu déroulant des modèles frontend, et le fournisseur.

Par défaut, Anthropic, OpenAI, Groq, et Ollama sont implémentés en tant que fournisseurs, mais la vidéo YouTube pour ce dépôt couvre comment étendre ceci pour fonctionner avec plus de fournisseurs si vous le souhaitez !

Lorsque vous ajoutez un nouveau modèle au tableau MODEL_LIST, il sera immédiatement disponible à l'utilisation lorsque vous exécutez l'application localement ou la rechargez. Pour les modèles Ollama, assurez-vous d'avoir le modèle installé avant d'essayer de l'utiliser ici !

## Scripts Disponibles

- `pnpm run dev` : Démarre le serveur de développement.
- `pnpm run build` : Construit le projet.
- `pnpm run start` : Exécute l'application construite localement en utilisant Wrangler Pages. Ce script utilise `bindings.sh` pour configurer les liaisons nécessaires afin que vous n'ayez pas à dupliquer les variables d'environnement.
- `pnpm run preview` : Construit le projet puis le démarre localement, utile pour tester la build de production. Notez que le streaming HTTP ne fonctionne pas comme prévu avec `wrangler pages dev`.
- `pnpm test` : Exécute la suite de tests en utilisant Vitest.
- `pnpm run typecheck` : Exécute la vérification des types TypeScript.
- `pnpm run typegen` : Génère les types TypeScript en utilisant Wrangler.
- `pnpm run deploy` : Construit le projet et le déploie sur Cloudflare Pages.

## Développement

Pour démarrer le serveur de développement :

```bash
pnpm run dev
```

Cela démarrera le serveur de développement Remix Vite. Vous aurez besoin de Google Chrome Canary pour exécuter ceci localement ! C'est une installation très facile et un bon navigateur pour le développement web de toute façon.

## Astuces et Conseils

Voici quelques conseils pour tirer le meilleur parti de Bolt.new :

- **Soyez spécifique sur votre stack** : Si vous souhaitez utiliser des frameworks ou bibliothèques spécifiques (comme Astro, Tailwind, ShadCN, ou tout autre framework JavaScript populaire), mentionnez-les dans votre prompt initial pour vous assurer que Bolt échafaude le projet en conséquence.

- **Utilisez l'icône de prompt d'amélioration** : Avant d'envoyer votre prompt, essayez de cliquer sur l'icône 'améliorer' pour que le modèle AI vous aide à affiner votre prompt, puis éditez les résultats avant de les soumettre.

- **Échafaudez les bases d'abord, puis ajoutez des fonctionnalités** : Assurez-vous que la structure de base de votre application est en place avant de plonger dans des fonctionnalités plus avancées. Cela aide Bolt à comprendre la fondation de votre projet et à s'assurer que tout est bien câblé avant de construire des fonctionnalités plus avancées.

- **Regroupez les instructions simples** : Gagnez du temps en combinant des instructions simples en un seul message. Par exemple, vous pouvez demander à Bolt de changer le schéma de couleurs, d'ajouter une réactivité mobile, et de redémarrer le serveur de développement, le tout en une seule fois, vous faisant gagner du temps et réduisant considérablement la consommation de crédits API.
133 changes: 92 additions & 41 deletions app/components/chat/BaseChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import { classNames } from '~/utils/classNames';
import { MODEL_LIST, DEFAULT_PROVIDER } from '~/utils/constants';
import { Messages } from './Messages.client';
import { SendButton } from './SendButton.client';
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { openDatabase, setProvider, getProviderById } from '~/lib/persistence/db';

import styles from './BaseChat.module.scss';
import type { Provider } from '~/types/provider';

const EXAMPLE_PROMPTS = [
{ text: 'Build a todo app in React using Tailwind' },
Expand All @@ -22,47 +24,101 @@ const EXAMPLE_PROMPTS = [
{ text: 'How do I center a div?' },
];

const providerList = [...new Set(MODEL_LIST.map((model) => model.provider))]
const ModelSelector = ({ model, setModel }) => {
const [providerList, setProviderList] = useState<Provider[]>([]);
const [selectedProvider, setSelectedProvider] = useState<Provider>();
const [apiKeyOrUrl, setApiKeyOrUrl] = useState<string>('');

useEffect(() => {
const loadProviders = async () => {
const providerIdList = [...new Set(MODEL_LIST.map((model) => model.provider))];
const db = await openDatabase();
if (db) {
const providerPromises = providerIdList.map(async (id) => {
return (await getProviderById(db, id)) || { id, models: MODEL_LIST.filter((m) => m.provider === id) };
});

setProviderList(await Promise.all(providerPromises));
setSelectedProvider(providerList.find((p) => p.id === DEFAULT_PROVIDER));
}
};
loadProviders();
}, []);

useEffect(() => {
if (selectedProvider) {
console.log('Selected Provider:', selectedProvider);
setApiKeyOrUrl(selectedProvider.apiKey ?? '');
}
}, [selectedProvider]);

const saveProvider = async () => {
if (selectedProvider) {
selectedProvider.apiKey = apiKeyOrUrl;
const db = await openDatabase();
if (db) {
await setProvider(db, selectedProvider);
}
}
};

const ModelSelector = ({ model, setModel, modelList, providerList }) => {
const [provider, setProvider] = useState(DEFAULT_PROVIDER);
return (
<div className="mb-2">
<select
value={provider}
onChange={(e) => {
setProvider(e.target.value);
const firstModel = [...modelList].find(m => m.provider == e.target.value);
setModel(firstModel ? firstModel.name : '');
}}
className="w-full p-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none"
>
{providerList.map((provider) => (
<option key={provider} value={provider}>
{provider}
</option>
))}
<option key="Ollama" value="Ollama">
Ollama
</option>
</select>
<select
value={model}
onChange={(e) => setModel(e.target.value)}
className="w-full p-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none"
>
{[...modelList].filter(e => e.provider == provider && e.name).map((modelOption) => (
<option key={modelOption.name} value={modelOption.name}>
{modelOption.label}
</option>
))}
</select>
<div className="flex mt-2">
<select
value={selectedProvider?.id}
onChange={(e) => {
const provider = providerList.find((p) => p.id === e.target.value);
setSelectedProvider(provider);
const firstModel = provider?.models.find((m) => m);
setModel(firstModel?.name);
}}
className="w-full p-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none"
>
{providerList.map((provider) => (
<option key={provider.id} value={provider.id}>
{provider.id}
</option>
))}
</select>
</div>
{selectedProvider?.id === 'Ollama' || (
<div className="flex mt-2">
<input
type="text"
value={apiKeyOrUrl}
placeholder="Custom URL"
onChange={(e) => setApiKeyOrUrl(e.target.value)}
className="w-full p-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none"
/>
<button
onClick={async (e) => {
await saveProvider();
}}
className="ml-2 p-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none"
>
Save
</button>
</div>
)}
<div className="flex mt-2">
<select
value={model}
onChange={(e) => setModel(e.target.value)}
className="w-full p-2 rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-prompt-background text-bolt-elements-textPrimary focus:outline-none"
>
{selectedProvider?.models.map((modelOption) => (
<option key={modelOption.name} value={modelOption.name}>
{modelOption.label}
</option>
))}
</select>
</div>
</div>
);
};

const TEXTAREA_MIN_HEIGHT = 76;

interface BaseChatProps {
textareaRef?: React.RefObject<HTMLTextAreaElement> | undefined;
messageRef?: RefCallback<HTMLDivElement> | undefined;
Expand Down Expand Up @@ -150,12 +206,7 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
'sticky bottom-0': chatStarted,
})}
>
<ModelSelector
model={model}
setModel={setModel}
modelList={MODEL_LIST}
providerList={providerList}
/>
<ModelSelector model={model} setModel={setModel} />
<div
className={classNames(
'shadow-sm border border-bolt-elements-borderColor bg-bolt-elements-prompt-background backdrop-filter backdrop-blur-[8px] rounded-lg overflow-hidden',
Expand Down Expand Up @@ -263,4 +314,4 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
</div>
);
},
);
);
19 changes: 15 additions & 4 deletions app/components/chat/Chat.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import { useAnimate } from 'framer-motion';
import { memo, useEffect, useRef, useState } from 'react';
import { cssTransition, toast, ToastContainer } from 'react-toastify';
import { useMessageParser, usePromptEnhancer, useShortcuts, useSnapScroll } from '~/lib/hooks';
import { useChatHistory } from '~/lib/persistence';
import { useChatHistory, openDatabase, getProviderById } from '~/lib/persistence';
import { chatStore } from '~/lib/stores/chat';
import { workbenchStore } from '~/lib/stores/workbench';
import { fileModificationsToHTML } from '~/utils/diff';
import { DEFAULT_MODEL } from '~/utils/constants';
import { MODEL_LIST, DEFAULT_MODEL } from '~/utils/constants';
import { cubicEasingFn } from '~/utils/easings';
import { createScopedLogger, renderLogger } from '~/utils/logger';
import { BaseChat } from './BaseChat';
import type { Provider } from '~/types/provider';

const toastAnimation = cssTransition({
enter: 'animated fadeInRight',
Expand Down Expand Up @@ -172,6 +173,16 @@ export const ChatImpl = memo(({ initialMessages, storeMessageHistory }: ChatProp

runAnimation();

let apiKey = '';
let provider: Provider | undefined;
const providerId = MODEL_LIST.find((m) => m.name === model)?.provider;
if (providerId) {
const db = await openDatabase();
if (db) {
provider = await getProviderById(db, providerId);
}
}

if (fileModifications !== undefined) {
const diff = fileModificationsToHTML(fileModifications);

Expand All @@ -182,15 +193,15 @@ export const ChatImpl = memo(({ initialMessages, storeMessageHistory }: ChatProp
* manually reset the input and we'd have to manually pass in file attachments. However, those
* aren't relevant here.
*/
append({ role: 'user', content: `[Model: ${model}]\n\n${diff}\n\n${_input}` });
append({ role: 'user', content: `[Model: ${model}][APIKey: ${provider?.apiKey || ''}]\n\n${diff}\n\n${_input}` });

/**
* After sending a new message we reset all modifications since the model
* should now be aware of all the changes.
*/
workbenchStore.resetAllFileModifications();
} else {
append({ role: 'user', content: `[Model: ${model}]\n\n${_input}` });
append({ role: 'user', content: `[Model: ${model}][APIKey: ${provider?.apiKey || ''}]\n\n${_input}` });
}

setInput('');
Expand Down
Loading

0 comments on commit d98264d

Please sign in to comment.