codacus commited on
Commit
fc4f89f
Β·
unverified Β·
1 Parent(s): 8185fd5

feat: add Starter template menu in homepage (#884)

Browse files

* added icons and component

* updated unocss to add dynamic icons

* removed temp logs

* updated readme

README.md CHANGED
@@ -62,6 +62,7 @@ bolt.diy was originally started by [Cole Medin](https://www.youtube.com/@ColeMed
62
  - βœ… Detect package.json and commands to auto install & run preview for folder and git import (@wonderwhy-er)
63
  - βœ… Selection tool to target changes visually (@emcconnell)
64
  - βœ… Detect terminal Errors and ask bolt to fix it (@thecodacus)
 
65
  - ⬜ **HIGH PRIORITY** - Prevent bolt from rewriting files as often (file locking and diffs)
66
  - ⬜ **HIGH PRIORITY** - Better prompting for smaller LLMs (code window sometimes doesn't start)
67
  - ⬜ **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
71
  - ⬜ Upload documents for knowledge - UI design templates, a code base to reference coding style, etc.
72
  - ⬜ Voice prompting
73
  - ⬜ Azure Open AI API Integration
74
- - ⬜ Perplexity Integration
75
  - ⬜ Vertex AI Integration
76
 
77
  ## Features
 
62
  - βœ… Detect package.json and commands to auto install & run preview for folder and git import (@wonderwhy-er)
63
  - βœ… Selection tool to target changes visually (@emcconnell)
64
  - βœ… Detect terminal Errors and ask bolt to fix it (@thecodacus)
65
+ - βœ… Add Starter Template Options (@thecodacus)
66
  - ⬜ **HIGH PRIORITY** - Prevent bolt from rewriting files as often (file locking and diffs)
67
  - ⬜ **HIGH PRIORITY** - Better prompting for smaller LLMs (code window sometimes doesn't start)
68
  - ⬜ **HIGH PRIORITY** - Run agents in the backend as opposed to a single model call
 
72
  - ⬜ Upload documents for knowledge - UI design templates, a code base to reference coding style, etc.
73
  - ⬜ Voice prompting
74
  - ⬜ Azure Open AI API Integration
75
+ - βœ… Perplexity Integration (@meetpateltech)
76
  - ⬜ Vertex AI Integration
77
 
78
  ## Features
app/components/chat/BaseChat.tsx CHANGED
@@ -28,6 +28,7 @@ import { SpeechRecognitionButton } from '~/components/chat/SpeechRecognition';
28
  import type { IProviderSetting, ProviderInfo } from '~/types/model';
29
  import { ScreenshotStateManager } from './ScreenshotStateManager';
30
  import { toast } from 'react-toastify';
 
31
  import type { ActionAlert } from '~/types/actions';
32
  import ChatAlert from './ChatAlert';
33
 
@@ -569,21 +570,24 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
569
  </div>
570
  </div>
571
  </div>
572
- {!chatStarted && (
573
- <div className="flex justify-center gap-2">
574
- {ImportButtons(importChat)}
575
- <GitCloneButton importChat={importChat} />
576
- </div>
577
- )}
578
- {!chatStarted &&
579
- ExamplePrompts((event, messageInput) => {
580
- if (isStreaming) {
581
- handleStop?.();
582
- return;
583
- }
584
-
585
- handleSendMessage?.(event, messageInput);
586
- })}
 
 
 
587
  </div>
588
  <ClientOnly>{() => <Workbench chatStarted={chatStarted} isStreaming={isStreaming} />}</ClientOnly>
589
  </div>
 
28
  import type { IProviderSetting, ProviderInfo } from '~/types/model';
29
  import { ScreenshotStateManager } from './ScreenshotStateManager';
30
  import { toast } from 'react-toastify';
31
+ import StarterTemplates from './StarterTemplates';
32
  import type { ActionAlert } from '~/types/actions';
33
  import ChatAlert from './ChatAlert';
34
 
 
570
  </div>
571
  </div>
572
  </div>
573
+ <div className="flex flex-col justify-center gap-5">
574
+ {!chatStarted && (
575
+ <div className="flex justify-center gap-2">
576
+ {ImportButtons(importChat)}
577
+ <GitCloneButton importChat={importChat} />
578
+ </div>
579
+ )}
580
+ {!chatStarted &&
581
+ ExamplePrompts((event, messageInput) => {
582
+ if (isStreaming) {
583
+ handleStop?.();
584
+ return;
585
+ }
586
+
587
+ handleSendMessage?.(event, messageInput);
588
+ })}
589
+ {!chatStarted && <StarterTemplates />}
590
+ </div>
591
  </div>
592
  <ClientOnly>{() => <Workbench chatStarted={chatStarted} isStreaming={isStreaming} />}</ClientOnly>
593
  </div>
app/components/chat/StarterTemplates.tsx ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import type { Template } from '~/types/template';
3
+ import { STARTER_TEMPLATES } from '~/utils/constants';
4
+
5
+ interface FrameworkLinkProps {
6
+ template: Template;
7
+ }
8
+
9
+ const FrameworkLink: React.FC<FrameworkLinkProps> = ({ template }) => (
10
+ <a
11
+ href={`/git?url=https://github.com/${template.githubRepo}.git`}
12
+ data-state="closed"
13
+ data-discover="true"
14
+ className="items-center justify-center "
15
+ >
16
+ <div
17
+ className={`inline-block ${template.icon} w-8 h-8 text-4xl transition-theme opacity-25 hover:opacity-75 transition-all`}
18
+ />
19
+ </a>
20
+ );
21
+
22
+ const StarterTemplates: React.FC = () => {
23
+ return (
24
+ <div className="flex flex-col items-center gap-4">
25
+ <span className="text-sm text-gray-500">or start a blank app with your favorite stack</span>
26
+ <div className="flex justify-center">
27
+ <div className="flex w-70 flex-wrap items-center justify-center gap-4">
28
+ {STARTER_TEMPLATES.map((template) => (
29
+ <FrameworkLink key={template.name} template={template} />
30
+ ))}
31
+ </div>
32
+ </div>
33
+ </div>
34
+ );
35
+ };
36
+
37
+ export default StarterTemplates;
app/components/settings/data/DataTab.tsx CHANGED
@@ -5,7 +5,6 @@ import { toast } from 'react-toastify';
5
  import { db, deleteById, getAll } from '~/lib/persistence';
6
  import { logStore } from '~/lib/stores/logs';
7
  import { classNames } from '~/utils/classNames';
8
- import styles from '~/components/settings/Settings.module.scss';
9
 
10
  // List of supported providers that can have API keys
11
  const API_KEY_PROVIDERS = [
@@ -25,8 +24,6 @@ const API_KEY_PROVIDERS = [
25
  'AzureOpenAI',
26
  ] as const;
27
 
28
- type Provider = typeof API_KEY_PROVIDERS[number];
29
-
30
  interface ApiKeys {
31
  [key: string]: string;
32
  }
@@ -52,6 +49,7 @@ export default function DataTab() {
52
  const error = new Error('Database is not available');
53
  logStore.logError('Failed to export chats - DB unavailable', error);
54
  toast.error('Database is not available');
 
55
  return;
56
  }
57
 
@@ -83,11 +81,13 @@ export default function DataTab() {
83
  const error = new Error('Database is not available');
84
  logStore.logError('Failed to delete chats - DB unavailable', error);
85
  toast.error('Database is not available');
 
86
  return;
87
  }
88
 
89
  try {
90
  setIsDeleting(true);
 
91
  const allChats = await getAll(db);
92
  await Promise.all(allChats.map((chat) => deleteById(db!, chat.id)));
93
  logStore.logSystem('All chats deleted successfully', { count: allChats.length });
@@ -125,16 +125,22 @@ export default function DataTab() {
125
 
126
  const handleImportSettings = (event: React.ChangeEvent<HTMLInputElement>) => {
127
  const file = event.target.files?.[0];
128
- if (!file) return;
 
 
 
129
 
130
  const reader = new FileReader();
 
131
  reader.onload = (e) => {
132
  try {
133
  const settings = JSON.parse(e.target?.result as string);
134
-
135
  Object.entries(settings).forEach(([key, value]) => {
136
  if (key === 'bolt_theme') {
137
- if (value) localStorage.setItem(key, value as string);
 
 
138
  } else if (value) {
139
  Cookies.set(key, value as string);
140
  }
@@ -152,14 +158,14 @@ export default function DataTab() {
152
 
153
  const handleExportApiKeyTemplate = () => {
154
  const template: ApiKeys = {};
155
- API_KEY_PROVIDERS.forEach(provider => {
156
  template[`${provider}_API_KEY`] = '';
157
  });
158
 
159
- template['OPENAI_LIKE_API_BASE_URL'] = '';
160
- template['LMSTUDIO_API_BASE_URL'] = '';
161
- template['OLLAMA_API_BASE_URL'] = '';
162
- template['TOGETHER_API_BASE_URL'] = '';
163
 
164
  downloadAsJson(template, 'api-keys-template.json');
165
  toast.success('API keys template exported successfully');
@@ -167,17 +173,22 @@ export default function DataTab() {
167
 
168
  const handleImportApiKeys = (event: React.ChangeEvent<HTMLInputElement>) => {
169
  const file = event.target.files?.[0];
170
- if (!file) return;
 
 
 
171
 
172
  const reader = new FileReader();
 
173
  reader.onload = (e) => {
174
  try {
175
  const apiKeys = JSON.parse(e.target?.result as string);
176
  let importedCount = 0;
177
  const consolidatedKeys: Record<string, string> = {};
178
 
179
- API_KEY_PROVIDERS.forEach(provider => {
180
  const keyName = `${provider}_API_KEY`;
 
181
  if (apiKeys[keyName]) {
182
  consolidatedKeys[provider] = apiKeys[keyName];
183
  importedCount++;
@@ -187,13 +198,14 @@ export default function DataTab() {
187
  if (importedCount > 0) {
188
  // Store all API keys in a single cookie as JSON
189
  Cookies.set('apiKeys', JSON.stringify(consolidatedKeys));
190
-
191
  // Also set individual cookies for backward compatibility
192
  Object.entries(consolidatedKeys).forEach(([provider, key]) => {
193
  Cookies.set(`${provider}_API_KEY`, key);
194
  });
195
 
196
  toast.success(`Successfully imported ${importedCount} API keys/URLs. Refreshing page to apply changes...`);
 
197
  // Reload the page after a short delay to allow the toast to be seen
198
  setTimeout(() => {
199
  window.location.reload();
@@ -203,12 +215,13 @@ export default function DataTab() {
203
  }
204
 
205
  // Set base URLs if they exist
206
- ['OPENAI_LIKE_API_BASE_URL', 'LMSTUDIO_API_BASE_URL', 'OLLAMA_API_BASE_URL', 'TOGETHER_API_BASE_URL'].forEach(baseUrl => {
207
- if (apiKeys[baseUrl]) {
208
- Cookies.set(baseUrl, apiKeys[baseUrl]);
209
- }
210
- });
211
-
 
212
  } catch (error) {
213
  toast.error('Failed to import API keys. Make sure the file is a valid JSON file.');
214
  console.error('Failed to import API keys:', error);
@@ -226,9 +239,7 @@ export default function DataTab() {
226
  <div className="flex flex-col gap-4">
227
  <div>
228
  <h4 className="text-bolt-elements-textPrimary mb-2">Chat History</h4>
229
- <p className="text-sm text-bolt-elements-textSecondary mb-4">
230
- Export or delete all your chat history.
231
- </p>
232
  <div className="flex gap-4">
233
  <button
234
  onClick={handleExportAllChats}
@@ -241,7 +252,7 @@ export default function DataTab() {
241
  disabled={isDeleting}
242
  className={classNames(
243
  'px-4 py-2 bg-bolt-elements-button-danger-background hover:bg-bolt-elements-button-danger-backgroundHover text-bolt-elements-button-danger-text rounded-lg transition-colors',
244
- isDeleting ? 'opacity-50 cursor-not-allowed' : ''
245
  )}
246
  >
247
  {isDeleting ? 'Deleting...' : 'Delete All Chats'}
@@ -263,12 +274,7 @@ export default function DataTab() {
263
  </button>
264
  <label className="px-4 py-2 bg-bolt-elements-button-primary-background hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-textPrimary rounded-lg transition-colors cursor-pointer">
265
  Import Settings
266
- <input
267
- type="file"
268
- accept=".json"
269
- onChange={handleImportSettings}
270
- className="hidden"
271
- />
272
  </label>
273
  </div>
274
  </div>
@@ -287,12 +293,7 @@ export default function DataTab() {
287
  </button>
288
  <label className="px-4 py-2 bg-bolt-elements-button-primary-background hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-textPrimary rounded-lg transition-colors cursor-pointer">
289
  Import API Keys
290
- <input
291
- type="file"
292
- accept=".json"
293
- onChange={handleImportApiKeys}
294
- className="hidden"
295
- />
296
  </label>
297
  </div>
298
  </div>
@@ -301,4 +302,4 @@ export default function DataTab() {
301
  </div>
302
  </div>
303
  );
304
- }
 
5
  import { db, deleteById, getAll } from '~/lib/persistence';
6
  import { logStore } from '~/lib/stores/logs';
7
  import { classNames } from '~/utils/classNames';
 
8
 
9
  // List of supported providers that can have API keys
10
  const API_KEY_PROVIDERS = [
 
24
  'AzureOpenAI',
25
  ] as const;
26
 
 
 
27
  interface ApiKeys {
28
  [key: string]: string;
29
  }
 
49
  const error = new Error('Database is not available');
50
  logStore.logError('Failed to export chats - DB unavailable', error);
51
  toast.error('Database is not available');
52
+
53
  return;
54
  }
55
 
 
81
  const error = new Error('Database is not available');
82
  logStore.logError('Failed to delete chats - DB unavailable', error);
83
  toast.error('Database is not available');
84
+
85
  return;
86
  }
87
 
88
  try {
89
  setIsDeleting(true);
90
+
91
  const allChats = await getAll(db);
92
  await Promise.all(allChats.map((chat) => deleteById(db!, chat.id)));
93
  logStore.logSystem('All chats deleted successfully', { count: allChats.length });
 
125
 
126
  const handleImportSettings = (event: React.ChangeEvent<HTMLInputElement>) => {
127
  const file = event.target.files?.[0];
128
+
129
+ if (!file) {
130
+ return;
131
+ }
132
 
133
  const reader = new FileReader();
134
+
135
  reader.onload = (e) => {
136
  try {
137
  const settings = JSON.parse(e.target?.result as string);
138
+
139
  Object.entries(settings).forEach(([key, value]) => {
140
  if (key === 'bolt_theme') {
141
+ if (value) {
142
+ localStorage.setItem(key, value as string);
143
+ }
144
  } else if (value) {
145
  Cookies.set(key, value as string);
146
  }
 
158
 
159
  const handleExportApiKeyTemplate = () => {
160
  const template: ApiKeys = {};
161
+ API_KEY_PROVIDERS.forEach((provider) => {
162
  template[`${provider}_API_KEY`] = '';
163
  });
164
 
165
+ template.OPENAI_LIKE_API_BASE_URL = '';
166
+ template.LMSTUDIO_API_BASE_URL = '';
167
+ template.OLLAMA_API_BASE_URL = '';
168
+ template.TOGETHER_API_BASE_URL = '';
169
 
170
  downloadAsJson(template, 'api-keys-template.json');
171
  toast.success('API keys template exported successfully');
 
173
 
174
  const handleImportApiKeys = (event: React.ChangeEvent<HTMLInputElement>) => {
175
  const file = event.target.files?.[0];
176
+
177
+ if (!file) {
178
+ return;
179
+ }
180
 
181
  const reader = new FileReader();
182
+
183
  reader.onload = (e) => {
184
  try {
185
  const apiKeys = JSON.parse(e.target?.result as string);
186
  let importedCount = 0;
187
  const consolidatedKeys: Record<string, string> = {};
188
 
189
+ API_KEY_PROVIDERS.forEach((provider) => {
190
  const keyName = `${provider}_API_KEY`;
191
+
192
  if (apiKeys[keyName]) {
193
  consolidatedKeys[provider] = apiKeys[keyName];
194
  importedCount++;
 
198
  if (importedCount > 0) {
199
  // Store all API keys in a single cookie as JSON
200
  Cookies.set('apiKeys', JSON.stringify(consolidatedKeys));
201
+
202
  // Also set individual cookies for backward compatibility
203
  Object.entries(consolidatedKeys).forEach(([provider, key]) => {
204
  Cookies.set(`${provider}_API_KEY`, key);
205
  });
206
 
207
  toast.success(`Successfully imported ${importedCount} API keys/URLs. Refreshing page to apply changes...`);
208
+
209
  // Reload the page after a short delay to allow the toast to be seen
210
  setTimeout(() => {
211
  window.location.reload();
 
215
  }
216
 
217
  // Set base URLs if they exist
218
+ ['OPENAI_LIKE_API_BASE_URL', 'LMSTUDIO_API_BASE_URL', 'OLLAMA_API_BASE_URL', 'TOGETHER_API_BASE_URL'].forEach(
219
+ (baseUrl) => {
220
+ if (apiKeys[baseUrl]) {
221
+ Cookies.set(baseUrl, apiKeys[baseUrl]);
222
+ }
223
+ },
224
+ );
225
  } catch (error) {
226
  toast.error('Failed to import API keys. Make sure the file is a valid JSON file.');
227
  console.error('Failed to import API keys:', error);
 
239
  <div className="flex flex-col gap-4">
240
  <div>
241
  <h4 className="text-bolt-elements-textPrimary mb-2">Chat History</h4>
242
+ <p className="text-sm text-bolt-elements-textSecondary mb-4">Export or delete all your chat history.</p>
 
 
243
  <div className="flex gap-4">
244
  <button
245
  onClick={handleExportAllChats}
 
252
  disabled={isDeleting}
253
  className={classNames(
254
  'px-4 py-2 bg-bolt-elements-button-danger-background hover:bg-bolt-elements-button-danger-backgroundHover text-bolt-elements-button-danger-text rounded-lg transition-colors',
255
+ isDeleting ? 'opacity-50 cursor-not-allowed' : '',
256
  )}
257
  >
258
  {isDeleting ? 'Deleting...' : 'Delete All Chats'}
 
274
  </button>
275
  <label className="px-4 py-2 bg-bolt-elements-button-primary-background hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-textPrimary rounded-lg transition-colors cursor-pointer">
276
  Import Settings
277
+ <input type="file" accept=".json" onChange={handleImportSettings} className="hidden" />
 
 
 
 
 
278
  </label>
279
  </div>
280
  </div>
 
293
  </button>
294
  <label className="px-4 py-2 bg-bolt-elements-button-primary-background hover:bg-bolt-elements-button-primary-backgroundHover text-bolt-elements-textPrimary rounded-lg transition-colors cursor-pointer">
295
  Import API Keys
296
+ <input type="file" accept=".json" onChange={handleImportApiKeys} className="hidden" />
 
 
 
 
 
297
  </label>
298
  </div>
299
  </div>
 
302
  </div>
303
  </div>
304
  );
305
+ }
app/types/template.ts ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ export interface Template {
2
+ name: string;
3
+ label: string;
4
+ description: string;
5
+ githubRepo: string;
6
+ tags?: string[];
7
+ icon?: string;
8
+ }
app/utils/constants.ts CHANGED
@@ -2,6 +2,7 @@ import type { IProviderSetting } from '~/types/model';
2
 
3
  import { LLMManager } from '~/lib/modules/llm/manager';
4
  import type { ModelInfo } from '~/lib/modules/llm/types';
 
5
 
6
  export const WORK_DIR_NAME = 'project';
7
  export const WORK_DIR = `/home/${WORK_DIR_NAME}`;
@@ -359,3 +360,96 @@ async function initializeModelList(options: {
359
 
360
  // initializeModelList({})
361
  export { initializeModelList, providerBaseUrlEnvKeys, MODEL_LIST };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  import { LLMManager } from '~/lib/modules/llm/manager';
4
  import type { ModelInfo } from '~/lib/modules/llm/types';
5
+ import type { Template } from '~/types/template';
6
 
7
  export const WORK_DIR_NAME = 'project';
8
  export const WORK_DIR = `/home/${WORK_DIR_NAME}`;
 
360
 
361
  // initializeModelList({})
362
  export { initializeModelList, providerBaseUrlEnvKeys, MODEL_LIST };
363
+
364
+ // starter Templates
365
+
366
+ export const STARTER_TEMPLATES: Template[] = [
367
+ {
368
+ name: 'bolt-astro-basic',
369
+ label: 'Astro Basic',
370
+ description: 'Lightweight Astro starter template for building fast static websites',
371
+ githubRepo: 'thecodacus/bolt-astro-basic-template',
372
+ tags: ['astro', 'blog', 'performance'],
373
+ icon: 'i-bolt:astro',
374
+ },
375
+ {
376
+ name: 'bolt-nextjs-shadcn',
377
+ label: 'Next.js with shadcn/ui',
378
+ description: 'Next.js starter fullstack template integrated with shadcn/ui components and styling system',
379
+ githubRepo: 'thecodacus/bolt-nextjs-shadcn-template',
380
+ tags: ['nextjs', 'react', 'typescript', 'shadcn', 'tailwind'],
381
+ icon: 'i-bolt:nextjs',
382
+ },
383
+ {
384
+ name: 'bolt-qwik-ts',
385
+ label: 'Qwik TypeScript',
386
+ description: 'Qwik framework starter with TypeScript for building resumable applications',
387
+ githubRepo: 'thecodacus/bolt-qwik-ts-template',
388
+ tags: ['qwik', 'typescript', 'performance', 'resumable'],
389
+ icon: 'i-bolt:qwik',
390
+ },
391
+ {
392
+ name: 'bolt-remix-ts',
393
+ label: 'Remix TypeScript',
394
+ description: 'Remix framework starter with TypeScript for full-stack web applications',
395
+ githubRepo: 'thecodacus/bolt-remix-ts-template',
396
+ tags: ['remix', 'typescript', 'fullstack', 'react'],
397
+ icon: 'i-bolt:remix',
398
+ },
399
+ {
400
+ name: 'bolt-slidev',
401
+ label: 'Slidev Presentation',
402
+ description: 'Slidev starter template for creating developer-friendly presentations using Markdown',
403
+ githubRepo: 'thecodacus/bolt-slidev-template',
404
+ tags: ['slidev', 'presentation', 'markdown'],
405
+ icon: 'i-bolt:slidev',
406
+ },
407
+ {
408
+ name: 'bolt-sveltekit',
409
+ label: 'SvelteKit',
410
+ description: 'SvelteKit starter template for building fast, efficient web applications',
411
+ githubRepo: 'bolt-sveltekit-template',
412
+ tags: ['svelte', 'sveltekit', 'typescript'],
413
+ icon: 'i-bolt:svelte',
414
+ },
415
+ {
416
+ name: 'vanilla-vite',
417
+ label: 'Vanilla + Vite',
418
+ description: 'Minimal Vite starter template for vanilla JavaScript projects',
419
+ githubRepo: 'thecodacus/vanilla-vite-template',
420
+ tags: ['vite', 'vanilla-js', 'minimal'],
421
+ icon: 'i-bolt:vite',
422
+ },
423
+ {
424
+ name: 'bolt-vite-react',
425
+ label: 'React + Vite + typescript',
426
+ description: 'React starter template powered by Vite for fast development experience',
427
+ githubRepo: 'thecodacus/bolt-vite-react-ts-template',
428
+ tags: ['react', 'vite', 'frontend'],
429
+ icon: 'i-bolt:react',
430
+ },
431
+ {
432
+ name: 'bolt-vite-ts',
433
+ label: 'Vite + TypeScript',
434
+ description: 'Vite starter template with TypeScript configuration for type-safe development',
435
+ githubRepo: 'thecodacus/bolt-vite-ts-template',
436
+ tags: ['vite', 'typescript', 'minimal'],
437
+ icon: 'i-bolt:typescript',
438
+ },
439
+ {
440
+ name: 'bolt-vue',
441
+ label: 'Vue.js',
442
+ description: 'Vue.js starter template with modern tooling and best practices',
443
+ githubRepo: 'thecodacus/bolt-vue-template',
444
+ tags: ['vue', 'typescript', 'frontend'],
445
+ icon: 'i-bolt:vue',
446
+ },
447
+ {
448
+ name: 'bolt-angular',
449
+ label: 'Angular Starter',
450
+ description: 'A modern Angular starter template with TypeScript support and best practices configuration',
451
+ githubRepo: 'thecodacus/bolt-angular-template',
452
+ tags: ['angular', 'typescript', 'frontend', 'spa'],
453
+ icon: 'i-bolt:angular',
454
+ },
455
+ ];
icons/angular.svg ADDED
icons/astro.svg ADDED
icons/nativescript.svg ADDED
icons/nextjs.svg ADDED
icons/nuxt.svg ADDED
icons/qwik.svg ADDED
icons/react.svg ADDED
icons/remix.svg ADDED
icons/remotion.svg ADDED
icons/slidev.svg ADDED
icons/svelte.svg ADDED
icons/typescript.svg ADDED
icons/vite.svg ADDED
icons/vue.svg ADDED
uno.config.ts CHANGED
@@ -98,6 +98,9 @@ const COLOR_PRIMITIVES = {
98
  };
99
 
100
  export default defineConfig({
 
 
 
101
  shortcuts: {
102
  'bolt-ease-cubic-bezier': 'ease-[cubic-bezier(0.4,0,0.2,1)]',
103
  'transition-theme': 'transition-[background-color,border-color,color] duration-150 bolt-ease-cubic-bezier',
 
98
  };
99
 
100
  export default defineConfig({
101
+ safelist: [
102
+ ...Object.keys(customIconCollection[collectionName]||{}).map(x=>`i-bolt:${x}`)
103
+ ],
104
  shortcuts: {
105
  'bolt-ease-cubic-bezier': 'ease-[cubic-bezier(0.4,0,0.2,1)]',
106
  'transition-theme': 'transition-[background-color,border-color,color] duration-150 bolt-ease-cubic-bezier',