feat: add Starter template menu in homepage (#884)
Some checks are pending
Update Stable Branch / prepare-release (push) Waiting to run

* added icons and component

* updated unocss to add dynamic icons

* removed temp logs

* updated readme
This commit is contained in:
Anirban Kar 2024-12-24 22:37:28 +05:30 committed by GitHub
parent 8185fd56ca
commit fc4f89f806
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 214 additions and 52 deletions

View File

@ -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

View File

@ -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<HTMLDivElement, BaseChatProps>(
</div>
</div>
</div>
{!chatStarted && (
<div className="flex justify-center gap-2">
{ImportButtons(importChat)}
<GitCloneButton importChat={importChat} />
</div>
)}
{!chatStarted &&
ExamplePrompts((event, messageInput) => {
if (isStreaming) {
handleStop?.();
return;
}
<div className="flex flex-col justify-center gap-5">
{!chatStarted && (
<div className="flex justify-center gap-2">
{ImportButtons(importChat)}
<GitCloneButton importChat={importChat} />
</div>
)}
{!chatStarted &&
ExamplePrompts((event, messageInput) => {
if (isStreaming) {
handleStop?.();
return;
}
handleSendMessage?.(event, messageInput);
})}
handleSendMessage?.(event, messageInput);
})}
{!chatStarted && <StarterTemplates />}
</div>
</div>
<ClientOnly>{() => <Workbench chatStarted={chatStarted} isStreaming={isStreaming} />}</ClientOnly>
</div>

View File

@ -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<FrameworkLinkProps> = ({ template }) => (
<a
href={`/git?url=https://github.com/${template.githubRepo}.git`}
data-state="closed"
data-discover="true"
className="items-center justify-center "
>
<div
className={`inline-block ${template.icon} w-8 h-8 text-4xl transition-theme opacity-25 hover:opacity-75 transition-all`}
/>
</a>
);
const StarterTemplates: React.FC = () => {
return (
<div className="flex flex-col items-center gap-4">
<span className="text-sm text-gray-500">or start a blank app with your favorite stack</span>
<div className="flex justify-center">
<div className="flex w-70 flex-wrap items-center justify-center gap-4">
{STARTER_TEMPLATES.map((template) => (
<FrameworkLink key={template.name} template={template} />
))}
</div>
</div>
</div>
);
};
export default StarterTemplates;

View File

@ -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<HTMLInputElement>) => {
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<HTMLInputElement>) => {
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<string, string> = {};
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() {
<div className="flex flex-col gap-4">
<div>
<h4 className="text-bolt-elements-textPrimary mb-2">Chat History</h4>
<p className="text-sm text-bolt-elements-textSecondary mb-4">
Export or delete all your chat history.
</p>
<p className="text-sm text-bolt-elements-textSecondary mb-4">Export or delete all your chat history.</p>
<div className="flex gap-4">
<button
onClick={handleExportAllChats}
@ -241,7 +252,7 @@ export default function DataTab() {
disabled={isDeleting}
className={classNames(
'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',
isDeleting ? 'opacity-50 cursor-not-allowed' : ''
isDeleting ? 'opacity-50 cursor-not-allowed' : '',
)}
>
{isDeleting ? 'Deleting...' : 'Delete All Chats'}
@ -263,12 +274,7 @@ export default function DataTab() {
</button>
<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">
Import Settings
<input
type="file"
accept=".json"
onChange={handleImportSettings}
className="hidden"
/>
<input type="file" accept=".json" onChange={handleImportSettings} className="hidden" />
</label>
</div>
</div>
@ -287,12 +293,7 @@ export default function DataTab() {
</button>
<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">
Import API Keys
<input
type="file"
accept=".json"
onChange={handleImportApiKeys}
className="hidden"
/>
<input type="file" accept=".json" onChange={handleImportApiKeys} className="hidden" />
</label>
</div>
</div>
@ -301,4 +302,4 @@ export default function DataTab() {
</div>
</div>
);
}
}

8
app/types/template.ts Normal file
View File

@ -0,0 +1,8 @@
export interface Template {
name: string;
label: string;
description: string;
githubRepo: string;
tags?: string[];
icon?: string;
}

View File

@ -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',
},
];

1
icons/angular.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' width='27' height='28' fill='none'><g clip-path='url(#a)'><path fill='#7B7B7B' d='m26.45 4.668-.955 14.998L16.363 0zM20.125 24.06l-6.9 3.937-6.9-3.937 1.403-3.401h10.994zm-6.9-16.596 3.616 8.79H9.609zm-12.28 12.2L0 4.669 10.087 0z'/></g><defs><clipPath id='a'><path fill='#fff' d='M0 0h26.45v28H0z'/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 364 B

1
icons/astro.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' width='28' height='28' fill='none'><path fill='#fff' d='M10.22 23.848c-1.265-1.156-1.635-3.586-1.108-5.346.914 1.11 2.18 1.461 3.492 1.66 2.024.306 4.013.191 5.893-.734.216-.106.415-.247.65-.39.176.512.222 1.03.16 1.555-.15 1.282-.787 2.271-1.801 3.022-.406.3-.835.568-1.254.85-1.286.87-1.634 1.89-1.151 3.373l.048.161a3.383 3.383 0 0 1-1.503-1.285 3.612 3.612 0 0 1-.58-1.962c-.004-.346-.004-.695-.05-1.036-.114-.832-.505-1.204-1.24-1.226-.755-.022-1.352.445-1.51 1.18-.013.056-.03.112-.048.177h.002Z'/><path fill='#7B7B7B' d='M3 18.21s3.746-1.825 7.502-1.825l2.832-8.765c.106-.424.415-.712.765-.712.35 0 .659.288.765.712l2.832 8.765c4.449 0 7.502 1.825 7.502 1.825L18.823.842C18.64.33 18.333 0 17.917 0h-7.635c-.416 0-.712.33-.907.842L3 18.21Z'/></svg>

After

Width:  |  Height:  |  Size: 794 B

1
icons/nativescript.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' width='29' height='28' fill='none'><g clip-path='url(#a)'><path fill='#7B7B7B' d='M26.523 2.051c1.317 1.317 2 2.966 2.05 4.949v14c-.05 1.982-.733 3.632-2.05 4.949-1.317 1.317-2.967 2-4.95 2.05h-14c-1.982-.05-3.631-.733-4.948-2.05C1.308 24.632.624 22.982.574 21V7c.05-1.982.734-3.632 2.05-4.949C3.943.734 5.592.051 7.575 0h14c1.982.05 3.632.734 4.949 2.051Zm-1.931 11.266c-.44-.438-.669-.987-.687-1.648V7c-.014-.66-.241-1.211-.68-1.65-.44-.44-.99-.667-1.652-.68h-2.33V16.33L9.904 4.67H7.574c-.661.014-1.211.24-1.651.68-.44.44-.666.99-.68 1.65v4.67c-.019.66-.247 1.21-.687 1.648-.44.437-.99.665-1.65.683.66.018 1.21.246 1.65.683.44.438.668.987.687 1.648V21c.014.66.24 1.211.68 1.65.44.44.99.667 1.65.68h2.332V11.67l9.337 11.662h2.331c.661-.014 1.212-.24 1.651-.68.44-.44.667-.99.68-1.65v-4.67c.019-.66.248-1.21.688-1.647.44-.438.99-.665 1.65-.684-.66-.018-1.21-.246-1.65-.683Z'/></g><defs><clipPath id='a'><path fill='#fff' d='M.574 0h28v28h-28z'/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1010 B

1
icons/nextjs.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' width='29' height='28' fill='none'><g clip-path='url(#a)'><mask id='b' width='29' height='28' x='0' y='0' maskUnits='userSpaceOnUse' style='mask-type:alpha'><path fill='#000' d='M14.573 28c7.732 0 14-6.268 14-14s-6.268-14-14-14-14 6.268-14 14 6.268 14 14 14Z'/></mask><g mask='url(#b)'><path fill='#7B7B7B' d='M14.573 28c7.732 0 14-6.268 14-14s-6.268-14-14-14-14 6.268-14 14 6.268 14 14 14Z'/><path fill='#fff' d='M23.83 24.503 11.33 8.4H8.973v11.196h1.884v-8.803l11.493 14.85a14.047 14.047 0 0 0 1.48-1.14Z'/><path fill='#fff' d='M20.33 8.4h-1.867v11.2l1.866.526V8.4Z'/></g></g><defs><clipPath id='a'><path fill='#fff' d='M.574 0h28v28h-28z'/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 708 B

1
icons/nuxt.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' width='28' height='28' fill='none'><path fill='#7B7B7B' d='M15.68 23.667h10.36c.33 0 .647-.116.933-.28.287-.164.582-.37.747-.654.165-.284.28-.605.28-.933 0-.328-.114-.65-.28-.933l-7-12.04a1.702 1.702 0 0 0-.653-.654 2.256 2.256 0 0 0-1.027-.28c-.33 0-.647.117-.933.28-.287.164-.488.37-.654.654l-1.773 3.08-3.547-5.974c-.165-.284-.367-.583-.653-.746-.286-.164-.603-.187-.933-.187-.33 0-.647.023-.934.187a2.213 2.213 0 0 0-.746.746L.187 20.867C.02 21.15 0 21.472 0 21.8c0 .328.021.65.187.933.165.284.46.49.746.654.287.164.603.28.934.28H8.4c2.589 0 4.473-1.162 5.787-3.36L17.36 14.8l1.68-2.893 5.133 8.773H17.36l-1.68 2.987ZM8.307 20.68H3.733l6.814-11.76L14 14.8l-2.287 3.988c-.873 1.426-1.867 1.892-3.406 1.892Z'/></svg>

After

Width:  |  Height:  |  Size: 758 B

1
icons/qwik.svg Normal file
View File

@ -0,0 +1 @@
<svg width='30' height='30' viewBox='0 0 30 30' fill='none' xmlns='http://www.w3.org/2000/svg'><path d='M24.8544 29.4143L19.6799 24.2665L19.6069 24.2793V24.2248L8.6028 13.3425L11.3199 10.7239L9.72188 1.56836L2.16084 10.9489C0.877089 12.2454 0.63008 14.3653 1.56013 15.9216L6.28578 23.7656C7.00825 24.9681 8.14357 25.7416 9.72884 25.6848C13.0849 25.5654 14.56 25.5654 14.56 25.5654L24.8521 29.412L24.8544 29.4155V29.4143Z' fill='#848484'/><path d='M27.4114 14.9893C28.1559 13.4527 28.4227 12.1087 27.6874 10.7576L26.6414 8.83259L26.0987 7.84455L25.8876 7.45954L25.8679 7.48158L23.0221 2.54487C22.3043 1.29591 20.9683 0.532847 19.5292 0.549082L17.0336 0.619822L9.58386 0.639537C8.17719 0.648814 6.87952 1.3968 6.16632 2.60865L1.64014 11.5984L9.74042 1.52088L20.3665 13.2057L18.4646 15.1331L19.5999 24.2747L19.6161 24.2539V24.2805H19.5999L19.6219 24.3026L20.5079 25.1654L24.7941 29.3518C24.9738 29.5257 25.2661 29.317 25.1466 29.1024L22.4968 23.8886' fill='#B4B4B4'/><path d='M20.3918 13.1765L9.73798 1.55078L11.2513 10.6542L8.54004 13.2855L19.5801 24.2571L18.5851 15.149L20.3918 13.1799V13.1765Z' fill='black'/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

1
icons/react.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' width='28' height='28' fill='none'><path fill='#7B7B7B' d='M26.573 13.944c0-1.628-2.039-3.17-5.164-4.127.721-3.186.4-5.72-1.012-6.532a2.196 2.196 0 0 0-1.122-.28v1.117c.23 0 .416.045.571.13.681.39.977 1.878.746 3.792-.055.47-.145.966-.255 1.472a24.273 24.273 0 0 0-3.18-.546 24.425 24.425 0 0 0-2.084-2.504c1.633-1.518 3.165-2.349 4.207-2.349V3c-1.377 0-3.18.982-5.004 2.685C12.453 3.992 10.65 3.02 9.273 3.02v1.117c1.036 0 2.574.826 4.207 2.334a23.639 23.639 0 0 0-2.069 2.5c-1.132.12-2.203.305-3.185.55-.115-.5-.2-.986-.26-1.452-.236-1.914.054-3.401.73-3.797.15-.09.346-.13.577-.13V3.025c-.421 0-.802.09-1.132.28-1.408.812-1.723 3.341-.997 6.517C4.029 10.784 2 12.322 2 13.944c0 1.628 2.039 3.17 5.164 4.127-.721 3.186-.4 5.72 1.012 6.532.325.19.706.28 1.127.28 1.377 0 3.18-.982 5.003-2.685 1.824 1.693 3.627 2.665 5.004 2.665.42 0 .802-.09 1.132-.28 1.407-.812 1.723-3.341.997-6.517 3.105-.956 5.134-2.5 5.134-4.122Zm-6.522-3.34a22.533 22.533 0 0 1-.676 1.978 27.086 27.086 0 0 0-1.377-2.374c.711.105 1.397.235 2.053.395Zm-2.294 5.334a26.71 26.71 0 0 1-1.207 1.913 26.066 26.066 0 0 1-4.518.005 26.128 26.128 0 0 1-2.254-3.897 26.685 26.685 0 0 1 2.244-3.912 26.051 26.051 0 0 1 4.518-.005 26.117 26.117 0 0 1 2.254 3.897 28.091 28.091 0 0 1-1.037 1.998Zm1.618-.652c.27.672.501 1.343.691 1.994-.656.16-1.347.295-2.063.4a27.72 27.72 0 0 0 1.372-2.394Zm-5.079 5.345a20.648 20.648 0 0 1-1.392-1.603c.45.02.912.035 1.377.035.471 0 .937-.01 1.393-.035-.451.586-.917 1.122-1.378 1.603Zm-3.726-2.95a22.6 22.6 0 0 1-2.054-.396c.186-.646.416-1.312.677-1.979.205.401.42.802.656 1.203.235.4.476.79.72 1.172ZM14.27 7.257c.466.481.932 1.017 1.393 1.603-.451-.02-.912-.035-1.378-.035-.47 0-.936.01-1.392.035.45-.586.916-1.122 1.377-1.603Zm-3.706 2.95a27.624 27.624 0 0 0-1.372 2.39c-.271-.671-.501-1.343-.692-1.994a24.32 24.32 0 0 1 2.064-.395Zm-4.533 6.271c-1.773-.756-2.92-1.748-2.92-2.534 0-.786 1.147-1.783 2.92-2.534.43-.186.902-.351 1.387-.506.286.982.662 2.003 1.127 3.05a23.715 23.715 0 0 0-1.112 3.035 15.23 15.23 0 0 1-1.402-.51Zm2.695 7.158c-.681-.39-.977-1.878-.747-3.792.055-.47.146-.966.256-1.472.982.24 2.053.425 3.18.546a24.43 24.43 0 0 0 2.084 2.504c-1.633 1.518-3.165 2.35-4.207 2.35a1.195 1.195 0 0 1-.566-.136Zm11.88-3.817c.236 1.914-.055 3.401-.73 3.797-.151.09-.346.13-.577.13-1.037 0-2.574-.826-4.207-2.334a23.668 23.668 0 0 0 2.068-2.5 23.393 23.393 0 0 0 3.186-.55c.115.506.205.992.26 1.457Zm1.929-3.34c-.431.185-.902.35-1.388.505a24.059 24.059 0 0 0-1.127-3.05c.461-1.042.832-2.058 1.112-3.035.496.155.967.325 1.408.51 1.773.757 2.92 1.749 2.92 2.535-.005.786-1.152 1.783-2.925 2.534Z'/><path fill='#7B7B7B' d='M14.281 16.236a2.289 2.289 0 1 0 0-4.578 2.289 2.289 0 0 0 0 4.578Z'/></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

1
icons/remix.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' width='28' height='28' fill='none'><g fill='#7B7B7B' clip-path='url(#a)'><path fill-rule='evenodd' d='M25.261 21.593c.252 3.235.252 4.752.252 6.407h-7.485c0-.36.006-.69.013-1.025.02-1.04.041-2.124-.127-4.313-.223-3.206-1.603-3.918-4.142-3.918H2V12.91h12.129c3.206 0 4.809-.975 4.809-3.557 0-2.27-1.603-3.647-4.81-3.647H2V0h13.465C22.723 0 26.33 3.428 26.33 8.904c0 4.096-2.538 6.768-5.967 7.213 2.894.579 4.586 2.226 4.898 5.476Z' clip-rule='evenodd'/><path d='M2 28v-4.348h7.914c1.322 0 1.61.98 1.61 1.566V28H2Z'/></g><defs><clipPath id='a'><path fill='#fff' d='M0 0h28v28H0z'/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 643 B

1
icons/remotion.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' width='28' height='28' fill='none'><g clip-path='url(#a)'><path fill='#4B4B4B' d='M6.087.002a4.874 4.874 0 0 0-1.531.306c-.237.089-.626.283-.84.417a4.72 4.72 0 0 0-1.93 2.384c-.07.194-.262.813-.386 1.24C.578 7.196.112 10.311.012 13.608a55.16 55.16 0 0 0 0 2.292 39.2 39.2 0 0 0 .654 6.172c.154.814.4 1.908.544 2.405a4.712 4.712 0 0 0 1.735 2.5 4.695 4.695 0 0 0 3.06.92c.463-.03 1.381-.153 2.127-.286 3.363-.6 6.47-1.673 9.287-3.21a26.336 26.336 0 0 0 4.78-3.305 23.893 23.893 0 0 0 3.817-4.177 5.32 5.32 0 0 0 .512-.825 4.6 4.6 0 0 0 .484-2.094c0-.698-.132-1.319-.417-1.954-.136-.306-.267-.526-.56-.944a23.833 23.833 0 0 0-3.738-4.178c-2.264-2.017-4.953-3.672-7.956-4.897a30.401 30.401 0 0 0-2.063-.75A32.404 32.404 0 0 0 6.91.05a5.412 5.412 0 0 0-.824-.048Z'/><path fill='#7B7B7B' d='M7.67 2.98c-.46.025-.83.098-1.204.24-.187.07-.492.224-.66.33a3.715 3.715 0 0 0-1.52 1.875c-.055.153-.206.64-.303.975-.647 2.242-1.014 4.693-1.093 7.288a43.377 43.377 0 0 0 0 1.803c.053 1.72.217 3.273.515 4.857a26.7 26.7 0 0 0 .428 1.893c.23.794.698 1.47 1.365 1.966a3.697 3.697 0 0 0 2.408.725 18.771 18.771 0 0 0 1.674-.226c2.646-.472 5.09-1.316 7.308-2.525a20.72 20.72 0 0 0 3.761-2.601 18.8 18.8 0 0 0 3.004-3.287c.201-.28.302-.443.403-.649a3.62 3.62 0 0 0 .38-1.648c0-.55-.103-1.038-.327-1.537-.108-.242-.21-.415-.441-.743a18.759 18.759 0 0 0-2.942-3.288c-1.781-1.588-3.897-2.89-6.26-3.853a23.91 23.91 0 0 0-1.624-.591 25.495 25.495 0 0 0-4.223-.966 4.259 4.259 0 0 0-.648-.038Z'/><path fill='#fff' d='M9.31 6.068c-.33.018-.597.07-.866.173a2.671 2.671 0 0 0-1.567 1.585c-.04.11-.149.46-.218.701-.465 1.612-.73 3.375-.786 5.24a31.225 31.225 0 0 0 0 1.298 22.18 22.18 0 0 0 .37 3.492 19.2 19.2 0 0 0 .308 1.362c.165.57.502 1.057.982 1.414a2.656 2.656 0 0 0 1.731.521c.262-.017.782-.087 1.204-.162 1.903-.34 3.661-.947 5.255-1.816a14.9 14.9 0 0 0 2.706-1.87 13.52 13.52 0 0 0 2.16-2.365c.144-.201.217-.319.29-.466.186-.382.274-.761.273-1.185 0-.396-.075-.747-.236-1.106a2.889 2.889 0 0 0-.317-.534 13.485 13.485 0 0 0-2.115-2.365c-1.281-1.141-2.803-2.078-4.502-2.77-.368-.15-.731-.283-1.168-.426a18.339 18.339 0 0 0-3.037-.694 3.063 3.063 0 0 0-.466-.027Z'/></g><defs><clipPath id='a'><path fill='#fff' d='M0 0h28v28H0z'/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

1
icons/slidev.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' width='29' height='28' fill='none'><g clip-path='url(#a)'><mask id='b' width='29' height='29' x='0' y='-1' maskUnits='userSpaceOnUse' style='mask-type:luminance'><path fill='#fff' d='M28.573-.002h-28v28h28v-28Z'/></mask><g mask='url(#b)'><path fill='#4B4B4B' d='M22.243 3.408H7.634A3.652 3.652 0 0 0 3.982 7.06v14.61a3.652 3.652 0 0 0 3.652 3.651h14.609a3.652 3.652 0 0 0 3.652-3.652V7.06a3.652 3.652 0 0 0-3.652-3.652Z'/><path fill='#7B7B7B' d='M22.486 10.955c0-6.052-4.905-10.957-10.956-10.957C5.479-.002.573 4.903.573 10.955c0 6.05 4.906 10.956 10.957 10.956 6.05 0 10.956-4.905 10.956-10.956Z'/><path fill='#fff' d='M14.239 15.563c-.287-1.07-.43-1.604-.288-1.974.123-.322.378-.576.7-.7.37-.141.904.002 1.974.288l5.315 1.425c1.07.286 1.604.43 1.853.737.217.268.31.616.256.956-.062.391-.453.782-1.236 1.565l-3.891 3.892c-.783.782-1.174 1.174-1.565 1.236-.34.054-.688-.04-.957-.257-.307-.248-.45-.783-.737-1.853l-1.424-5.315Z'/></g></g><defs><clipPath id='a'><path fill='#fff' d='M.574 0h28v28h-28z'/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

1
icons/svelte.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' width='28' height='28' fill='none'><g clip-path='url(#a)'><path fill='#7B7B7B' fill-rule='evenodd' d='M12.352 1.237c3.701-2.349 8.85-1.258 11.437 2.468a7.818 7.818 0 0 1 1.352 6.05 7.164 7.164 0 0 1-1.115 2.8c.83 1.543 1.092 3.323.783 5.055a7.417 7.417 0 0 1-3.37 5.007l-6.525 4.152c-3.701 2.35-8.827 1.258-11.437-2.467a7.984 7.984 0 0 1-1.352-6.028 7.164 7.164 0 0 1 1.115-2.8c-.83-1.542-1.092-3.322-.783-5.054a7.416 7.416 0 0 1 3.37-5.007l6.525-4.176Zm-6.193 21.36a5.172 5.172 0 0 0 5.553 2.065 5.166 5.166 0 0 0 1.329-.593l6.525-4.153a4.493 4.493 0 0 0 2.04-3.014c.214-1.28-.07-2.586-.83-3.63a5.172 5.172 0 0 0-5.552-2.065 5.166 5.166 0 0 0-1.329.594l-2.492 1.59c-.118.07-.26.118-.403.166a1.557 1.557 0 0 1-1.685-.617 1.473 1.473 0 0 1-.237-1.092c.071-.38.285-.688.617-.902l6.502-4.152c.118-.071.26-.119.403-.166a1.557 1.557 0 0 1 1.685.617c.19.285.285.64.26.973l-.023.237.237.071c.926.285 1.78.712 2.563 1.282l.332.237.119-.38a5.95 5.95 0 0 0 .166-.617c.214-1.281-.071-2.586-.83-3.63a5.172 5.172 0 0 0-5.553-2.065 5.164 5.164 0 0 0-1.329.594L7.702 8.099a4.493 4.493 0 0 0-2.04 3.014 4.793 4.793 0 0 0 .806 3.63 5.172 5.172 0 0 0 5.552 2.065 4.413 4.413 0 0 0 1.33-.57l2.49-1.59c.12-.071.262-.118.404-.166a1.557 1.557 0 0 1 1.685.617c.213.309.308.712.237 1.092-.071.38-.285.688-.617.901l-6.502 4.153a2.047 2.047 0 0 1-.403.166 1.57 1.57 0 0 1-1.685-.64 1.588 1.588 0 0 1-.26-.974l.023-.237-.237-.071a8.654 8.654 0 0 1-2.563-1.282l-.332-.237-.119.38c-.023.107-.047.208-.07.308-.025.101-.048.202-.072.309-.214 1.281.071 2.586.83 3.63Z' clip-rule='evenodd'/></g><defs><clipPath id='a'><path fill='#fff' d='M0 0h28v28H0z'/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

1
icons/typescript.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' width='29' height='29' fill='none'><g clip-path='url(#a)'><path fill='#7B7B7B' d='M.607 14.5V.5h28v28h-28'/><path fill='#fff' d='M6.747 14.55v1.14h3.64v10.36h2.583V15.69h3.64v-1.12c0-.63 0-1.14-.028-1.155 0-.02-2.22-.028-4.914-.028l-4.9.021v1.148l-.021-.007Zm16.359-1.17a3.39 3.39 0 0 1 1.75 1.001c.258.28.643.77.671.896 0 .042-1.21.861-1.945 1.316-.028.021-.14-.098-.252-.28-.364-.518-.735-.742-1.316-.784-.84-.056-1.4.385-1.4 1.12 0 .224.042.35.126.532.189.385.539.616 1.624 1.092 2.002.861 2.87 1.428 3.395 2.24.595.91.727 2.338.328 3.41-.447 1.168-1.54 1.96-3.1 2.218-.49.084-1.61.07-2.135-.02-1.12-.21-2.191-.77-2.85-1.492-.258-.28-.755-1.029-.727-1.078l.266-.168 1.05-.609.79-.462.183.245c.23.364.749.854 1.05 1.022.91.47 2.128.406 2.73-.14.259-.238.37-.49.37-.84 0-.322-.048-.469-.21-.714-.223-.308-.671-.56-1.931-1.12-1.45-.616-2.065-1.008-2.64-1.61-.328-.364-.63-.93-.77-1.4-.104-.406-.14-1.4-.041-1.799.3-1.4 1.358-2.38 2.87-2.66.49-.098 1.645-.056 2.128.07l-.014.014Z'/></g><defs><clipPath id='a'><path fill='#fff' d='M.607.5h28v28h-28z'/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

1
icons/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' width='28' height='28' fill='none'><path fill='#7B7B7B' d='M26.914 4.865 14.7 26.771a.663.663 0 0 1-1.155.005L1.088 4.867a.665.665 0 0 1 .693-.985L14.01 6.074a.662.662 0 0 0 .236 0l11.97-2.189a.665.665 0 0 1 .699.98Z'/><path fill='#fff' d='m19.833 1.006-9.038 1.777a.332.332 0 0 0-.268.307l-.556 9.418a.332.332 0 0 0 .406.344l2.517-.582a.332.332 0 0 1 .4.39l-.748 3.673a.332.332 0 0 0 .421.385l1.555-.474a.332.332 0 0 1 .421.386l-1.188 5.768c-.074.36.404.558.604.248l.133-.206 7.365-14.743a.333.333 0 0 0-.36-.476l-2.59.502a.333.333 0 0 1-.382-.42l1.69-5.878a.333.333 0 0 0-.382-.419Z'/></svg>

After

Width:  |  Height:  |  Size: 633 B

1
icons/vue.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' width='28' height='28' fill='none'><path fill='#7B7B7B' d='M22.398 2h5.6l-14 24.148L0 2h10.709l3.29 5.6 3.22-5.6h5.179Z'/><path fill='#fff' d='m0 2 13.999 24.148L27.997 2h-5.6L14 16.489 5.529 2H0Z'/></svg>

After

Width:  |  Height:  |  Size: 245 B

View File

@ -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',