From fc4f89f806a62fecac5fbe4d41fbc26f2ea4ea35 Mon Sep 17 00:00:00 2001 From: Anirban Kar Date: Tue, 24 Dec 2024 22:37:28 +0530 Subject: [PATCH] feat: add Starter template menu in homepage (#884) * added icons and component * updated unocss to add dynamic icons * removed temp logs * updated readme --- README.md | 3 +- app/components/chat/BaseChat.tsx | 32 ++++---- app/components/chat/StarterTemplates.tsx | 37 ++++++++++ app/components/settings/data/DataTab.tsx | 75 +++++++++---------- app/types/template.ts | 8 ++ app/utils/constants.ts | 94 ++++++++++++++++++++++++ icons/angular.svg | 1 + icons/astro.svg | 1 + icons/nativescript.svg | 1 + icons/nextjs.svg | 1 + icons/nuxt.svg | 1 + icons/qwik.svg | 1 + icons/react.svg | 1 + icons/remix.svg | 1 + icons/remotion.svg | 1 + icons/slidev.svg | 1 + icons/svelte.svg | 1 + icons/typescript.svg | 1 + icons/vite.svg | 1 + icons/vue.svg | 1 + uno.config.ts | 3 + 21 files changed, 214 insertions(+), 52 deletions(-) create mode 100644 app/components/chat/StarterTemplates.tsx create mode 100644 app/types/template.ts create mode 100644 icons/angular.svg create mode 100644 icons/astro.svg create mode 100644 icons/nativescript.svg create mode 100644 icons/nextjs.svg create mode 100644 icons/nuxt.svg create mode 100644 icons/qwik.svg create mode 100644 icons/react.svg create mode 100644 icons/remix.svg create mode 100644 icons/remotion.svg create mode 100644 icons/slidev.svg create mode 100644 icons/svelte.svg create mode 100644 icons/typescript.svg create mode 100644 icons/vite.svg create mode 100644 icons/vue.svg diff --git a/README.md b/README.md index 994a494..5a4896e 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ bolt.diy was originally started by [Cole Medin](https://www.youtube.com/@ColeMed - ✅ Detect package.json and commands to auto install & run preview for folder and git import (@wonderwhy-er) - ✅ Selection tool to target changes visually (@emcconnell) - ✅ Detect terminal Errors and ask bolt to fix it (@thecodacus) +- ✅ Add Starter Template Options (@thecodacus) - ⬜ **HIGH PRIORITY** - Prevent bolt from rewriting files as often (file locking and diffs) - ⬜ **HIGH PRIORITY** - Better prompting for smaller LLMs (code window sometimes doesn't start) - ⬜ **HIGH PRIORITY** - Run agents in the backend as opposed to a single model call @@ -71,7 +72,7 @@ bolt.diy was originally started by [Cole Medin](https://www.youtube.com/@ColeMed - ⬜ Upload documents for knowledge - UI design templates, a code base to reference coding style, etc. - ⬜ Voice prompting - ⬜ Azure Open AI API Integration -- ⬜ Perplexity Integration +- ✅ Perplexity Integration (@meetpateltech) - ⬜ Vertex AI Integration ## Features diff --git a/app/components/chat/BaseChat.tsx b/app/components/chat/BaseChat.tsx index 422c6e4..57c8829 100644 --- a/app/components/chat/BaseChat.tsx +++ b/app/components/chat/BaseChat.tsx @@ -28,6 +28,7 @@ import { SpeechRecognitionButton } from '~/components/chat/SpeechRecognition'; import type { IProviderSetting, ProviderInfo } from '~/types/model'; import { ScreenshotStateManager } from './ScreenshotStateManager'; import { toast } from 'react-toastify'; +import StarterTemplates from './StarterTemplates'; import type { ActionAlert } from '~/types/actions'; import ChatAlert from './ChatAlert'; @@ -569,21 +570,24 @@ export const BaseChat = React.forwardRef( - {!chatStarted && ( -
- {ImportButtons(importChat)} - -
- )} - {!chatStarted && - ExamplePrompts((event, messageInput) => { - if (isStreaming) { - handleStop?.(); - return; - } +
+ {!chatStarted && ( +
+ {ImportButtons(importChat)} + +
+ )} + {!chatStarted && + ExamplePrompts((event, messageInput) => { + if (isStreaming) { + handleStop?.(); + return; + } - handleSendMessage?.(event, messageInput); - })} + handleSendMessage?.(event, messageInput); + })} + {!chatStarted && } +
{() => } diff --git a/app/components/chat/StarterTemplates.tsx b/app/components/chat/StarterTemplates.tsx new file mode 100644 index 0000000..b48c92c --- /dev/null +++ b/app/components/chat/StarterTemplates.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import type { Template } from '~/types/template'; +import { STARTER_TEMPLATES } from '~/utils/constants'; + +interface FrameworkLinkProps { + template: Template; +} + +const FrameworkLink: React.FC = ({ template }) => ( + +
+ +); + +const StarterTemplates: React.FC = () => { + return ( +
+ or start a blank app with your favorite stack +
+
+ {STARTER_TEMPLATES.map((template) => ( + + ))} +
+
+
+ ); +}; + +export default StarterTemplates; diff --git a/app/components/settings/data/DataTab.tsx b/app/components/settings/data/DataTab.tsx index 756abaa..aac2fe0 100644 --- a/app/components/settings/data/DataTab.tsx +++ b/app/components/settings/data/DataTab.tsx @@ -5,7 +5,6 @@ import { toast } from 'react-toastify'; import { db, deleteById, getAll } from '~/lib/persistence'; import { logStore } from '~/lib/stores/logs'; import { classNames } from '~/utils/classNames'; -import styles from '~/components/settings/Settings.module.scss'; // List of supported providers that can have API keys const API_KEY_PROVIDERS = [ @@ -25,8 +24,6 @@ const API_KEY_PROVIDERS = [ 'AzureOpenAI', ] as const; -type Provider = typeof API_KEY_PROVIDERS[number]; - interface ApiKeys { [key: string]: string; } @@ -52,6 +49,7 @@ export default function DataTab() { const error = new Error('Database is not available'); logStore.logError('Failed to export chats - DB unavailable', error); toast.error('Database is not available'); + return; } @@ -83,11 +81,13 @@ export default function DataTab() { const error = new Error('Database is not available'); logStore.logError('Failed to delete chats - DB unavailable', error); toast.error('Database is not available'); + return; } try { setIsDeleting(true); + const allChats = await getAll(db); await Promise.all(allChats.map((chat) => deleteById(db!, chat.id))); logStore.logSystem('All chats deleted successfully', { count: allChats.length }); @@ -125,16 +125,22 @@ export default function DataTab() { const handleImportSettings = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; - if (!file) return; + + if (!file) { + return; + } const reader = new FileReader(); + reader.onload = (e) => { try { const settings = JSON.parse(e.target?.result as string); - + Object.entries(settings).forEach(([key, value]) => { if (key === 'bolt_theme') { - if (value) localStorage.setItem(key, value as string); + if (value) { + localStorage.setItem(key, value as string); + } } else if (value) { Cookies.set(key, value as string); } @@ -152,14 +158,14 @@ export default function DataTab() { const handleExportApiKeyTemplate = () => { const template: ApiKeys = {}; - API_KEY_PROVIDERS.forEach(provider => { + API_KEY_PROVIDERS.forEach((provider) => { template[`${provider}_API_KEY`] = ''; }); - template['OPENAI_LIKE_API_BASE_URL'] = ''; - template['LMSTUDIO_API_BASE_URL'] = ''; - template['OLLAMA_API_BASE_URL'] = ''; - template['TOGETHER_API_BASE_URL'] = ''; + template.OPENAI_LIKE_API_BASE_URL = ''; + template.LMSTUDIO_API_BASE_URL = ''; + template.OLLAMA_API_BASE_URL = ''; + template.TOGETHER_API_BASE_URL = ''; downloadAsJson(template, 'api-keys-template.json'); toast.success('API keys template exported successfully'); @@ -167,17 +173,22 @@ export default function DataTab() { const handleImportApiKeys = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; - if (!file) return; + + if (!file) { + return; + } const reader = new FileReader(); + reader.onload = (e) => { try { const apiKeys = JSON.parse(e.target?.result as string); let importedCount = 0; const consolidatedKeys: Record = {}; - API_KEY_PROVIDERS.forEach(provider => { + API_KEY_PROVIDERS.forEach((provider) => { const keyName = `${provider}_API_KEY`; + if (apiKeys[keyName]) { consolidatedKeys[provider] = apiKeys[keyName]; importedCount++; @@ -187,13 +198,14 @@ export default function DataTab() { if (importedCount > 0) { // Store all API keys in a single cookie as JSON Cookies.set('apiKeys', JSON.stringify(consolidatedKeys)); - + // Also set individual cookies for backward compatibility Object.entries(consolidatedKeys).forEach(([provider, key]) => { Cookies.set(`${provider}_API_KEY`, key); }); toast.success(`Successfully imported ${importedCount} API keys/URLs. Refreshing page to apply changes...`); + // Reload the page after a short delay to allow the toast to be seen setTimeout(() => { window.location.reload(); @@ -203,12 +215,13 @@ export default function DataTab() { } // Set base URLs if they exist - ['OPENAI_LIKE_API_BASE_URL', 'LMSTUDIO_API_BASE_URL', 'OLLAMA_API_BASE_URL', 'TOGETHER_API_BASE_URL'].forEach(baseUrl => { - if (apiKeys[baseUrl]) { - Cookies.set(baseUrl, apiKeys[baseUrl]); - } - }); - + ['OPENAI_LIKE_API_BASE_URL', 'LMSTUDIO_API_BASE_URL', 'OLLAMA_API_BASE_URL', 'TOGETHER_API_BASE_URL'].forEach( + (baseUrl) => { + if (apiKeys[baseUrl]) { + Cookies.set(baseUrl, apiKeys[baseUrl]); + } + }, + ); } catch (error) { toast.error('Failed to import API keys. Make sure the file is a valid JSON file.'); console.error('Failed to import API keys:', error); @@ -226,9 +239,7 @@ export default function DataTab() {

Chat History

-

- Export or delete all your chat history. -

+

Export or delete all your chat history.

@@ -287,12 +293,7 @@ export default function DataTab() {
@@ -301,4 +302,4 @@ export default function DataTab() { ); -} \ No newline at end of file +} diff --git a/app/types/template.ts b/app/types/template.ts new file mode 100644 index 0000000..2194c7b --- /dev/null +++ b/app/types/template.ts @@ -0,0 +1,8 @@ +export interface Template { + name: string; + label: string; + description: string; + githubRepo: string; + tags?: string[]; + icon?: string; +} diff --git a/app/utils/constants.ts b/app/utils/constants.ts index 64e08a7..be83083 100644 --- a/app/utils/constants.ts +++ b/app/utils/constants.ts @@ -2,6 +2,7 @@ import type { IProviderSetting } from '~/types/model'; import { LLMManager } from '~/lib/modules/llm/manager'; import type { ModelInfo } from '~/lib/modules/llm/types'; +import type { Template } from '~/types/template'; export const WORK_DIR_NAME = 'project'; export const WORK_DIR = `/home/${WORK_DIR_NAME}`; @@ -359,3 +360,96 @@ async function initializeModelList(options: { // initializeModelList({}) export { initializeModelList, providerBaseUrlEnvKeys, MODEL_LIST }; + +// starter Templates + +export const STARTER_TEMPLATES: Template[] = [ + { + name: 'bolt-astro-basic', + label: 'Astro Basic', + description: 'Lightweight Astro starter template for building fast static websites', + githubRepo: 'thecodacus/bolt-astro-basic-template', + tags: ['astro', 'blog', 'performance'], + icon: 'i-bolt:astro', + }, + { + name: 'bolt-nextjs-shadcn', + label: 'Next.js with shadcn/ui', + description: 'Next.js starter fullstack template integrated with shadcn/ui components and styling system', + githubRepo: 'thecodacus/bolt-nextjs-shadcn-template', + tags: ['nextjs', 'react', 'typescript', 'shadcn', 'tailwind'], + icon: 'i-bolt:nextjs', + }, + { + name: 'bolt-qwik-ts', + label: 'Qwik TypeScript', + description: 'Qwik framework starter with TypeScript for building resumable applications', + githubRepo: 'thecodacus/bolt-qwik-ts-template', + tags: ['qwik', 'typescript', 'performance', 'resumable'], + icon: 'i-bolt:qwik', + }, + { + name: 'bolt-remix-ts', + label: 'Remix TypeScript', + description: 'Remix framework starter with TypeScript for full-stack web applications', + githubRepo: 'thecodacus/bolt-remix-ts-template', + tags: ['remix', 'typescript', 'fullstack', 'react'], + icon: 'i-bolt:remix', + }, + { + name: 'bolt-slidev', + label: 'Slidev Presentation', + description: 'Slidev starter template for creating developer-friendly presentations using Markdown', + githubRepo: 'thecodacus/bolt-slidev-template', + tags: ['slidev', 'presentation', 'markdown'], + icon: 'i-bolt:slidev', + }, + { + name: 'bolt-sveltekit', + label: 'SvelteKit', + description: 'SvelteKit starter template for building fast, efficient web applications', + githubRepo: 'bolt-sveltekit-template', + tags: ['svelte', 'sveltekit', 'typescript'], + icon: 'i-bolt:svelte', + }, + { + name: 'vanilla-vite', + label: 'Vanilla + Vite', + description: 'Minimal Vite starter template for vanilla JavaScript projects', + githubRepo: 'thecodacus/vanilla-vite-template', + tags: ['vite', 'vanilla-js', 'minimal'], + icon: 'i-bolt:vite', + }, + { + name: 'bolt-vite-react', + label: 'React + Vite + typescript', + description: 'React starter template powered by Vite for fast development experience', + githubRepo: 'thecodacus/bolt-vite-react-ts-template', + tags: ['react', 'vite', 'frontend'], + icon: 'i-bolt:react', + }, + { + name: 'bolt-vite-ts', + label: 'Vite + TypeScript', + description: 'Vite starter template with TypeScript configuration for type-safe development', + githubRepo: 'thecodacus/bolt-vite-ts-template', + tags: ['vite', 'typescript', 'minimal'], + icon: 'i-bolt:typescript', + }, + { + name: 'bolt-vue', + label: 'Vue.js', + description: 'Vue.js starter template with modern tooling and best practices', + githubRepo: 'thecodacus/bolt-vue-template', + tags: ['vue', 'typescript', 'frontend'], + icon: 'i-bolt:vue', + }, + { + name: 'bolt-angular', + label: 'Angular Starter', + description: 'A modern Angular starter template with TypeScript support and best practices configuration', + githubRepo: 'thecodacus/bolt-angular-template', + tags: ['angular', 'typescript', 'frontend', 'spa'], + icon: 'i-bolt:angular', + }, +]; diff --git a/icons/angular.svg b/icons/angular.svg new file mode 100644 index 0000000..0f39575 --- /dev/null +++ b/icons/angular.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/astro.svg b/icons/astro.svg new file mode 100644 index 0000000..cb21d98 --- /dev/null +++ b/icons/astro.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/nativescript.svg b/icons/nativescript.svg new file mode 100644 index 0000000..0631bf1 --- /dev/null +++ b/icons/nativescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/nextjs.svg b/icons/nextjs.svg new file mode 100644 index 0000000..1bb5425 --- /dev/null +++ b/icons/nextjs.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/nuxt.svg b/icons/nuxt.svg new file mode 100644 index 0000000..9aee163 --- /dev/null +++ b/icons/nuxt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/qwik.svg b/icons/qwik.svg new file mode 100644 index 0000000..040eec2 --- /dev/null +++ b/icons/qwik.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/react.svg b/icons/react.svg new file mode 100644 index 0000000..22c1d57 --- /dev/null +++ b/icons/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/remix.svg b/icons/remix.svg new file mode 100644 index 0000000..0ec88d3 --- /dev/null +++ b/icons/remix.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/remotion.svg b/icons/remotion.svg new file mode 100644 index 0000000..d35f4dd --- /dev/null +++ b/icons/remotion.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/slidev.svg b/icons/slidev.svg new file mode 100644 index 0000000..4fc134c --- /dev/null +++ b/icons/slidev.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/svelte.svg b/icons/svelte.svg new file mode 100644 index 0000000..7ee89b4 --- /dev/null +++ b/icons/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/typescript.svg b/icons/typescript.svg new file mode 100644 index 0000000..1a43b55 --- /dev/null +++ b/icons/typescript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/vite.svg b/icons/vite.svg new file mode 100644 index 0000000..0d7d42c --- /dev/null +++ b/icons/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/icons/vue.svg b/icons/vue.svg new file mode 100644 index 0000000..928f8a4 --- /dev/null +++ b/icons/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/uno.config.ts b/uno.config.ts index 56c98a6..d8ac5a9 100644 --- a/uno.config.ts +++ b/uno.config.ts @@ -98,6 +98,9 @@ const COLOR_PRIMITIVES = { }; export default defineConfig({ + safelist: [ + ...Object.keys(customIconCollection[collectionName]||{}).map(x=>`i-bolt:${x}`) + ], shortcuts: { 'bolt-ease-cubic-bezier': 'ease-[cubic-bezier(0.4,0,0.2,1)]', 'transition-theme': 'transition-[background-color,border-color,color] duration-150 bolt-ease-cubic-bezier',