| import ignore from 'ignore';
|
| import type { ProviderInfo } from '~/types/model';
|
| import type { Template } from '~/types/template';
|
| import { STARTER_TEMPLATES } from './constants';
|
| import Cookies from 'js-cookie';
|
|
|
| const starterTemplateSelectionPrompt = (templates: Template[]) => `
|
| You are an experienced developer who helps people choose the best starter template for their projects.
|
|
|
| Available templates:
|
| <template>
|
| <name>blank</name>
|
| <description>Empty starter for simple scripts and trivial tasks that don't require a full template setup</description>
|
| <tags>basic, script</tags>
|
| </template>
|
| ${templates
|
| .map(
|
| (template) => `
|
| <template>
|
| <name>${template.name}</name>
|
| <description>${template.description}</description>
|
| ${template.tags ? `<tags>${template.tags.join(', ')}</tags>` : ''}
|
| </template>
|
| `,
|
| )
|
| .join('\n')}
|
|
|
| Response Format:
|
| <selection>
|
| <templateName>{selected template name}</templateName>
|
| <title>{a proper title for the project}</title>
|
| </selection>
|
|
|
| Examples:
|
|
|
| <example>
|
| User: I need to build a todo app
|
| Response:
|
| <selection>
|
| <templateName>react-basic-starter</templateName>
|
| <title>Simple React todo application</title>
|
| </selection>
|
| </example>
|
|
|
| <example>
|
| User: Write a script to generate numbers from 1 to 100
|
| Response:
|
| <selection>
|
| <templateName>blank</templateName>
|
| <title>script to generate numbers from 1 to 100</title>
|
| </selection>
|
| </example>
|
|
|
| Instructions:
|
| 1. For trivial tasks and simple scripts, always recommend the blank template
|
| 2. For more complex projects, recommend templates from the provided list
|
| 3. Follow the exact XML format
|
| 4. Consider both technical requirements and tags
|
| 5. If no perfect match exists, recommend the closest option
|
|
|
| Important: Provide only the selection tags in your response, no additional text.
|
| `;
|
|
|
| const templates: Template[] = STARTER_TEMPLATES.filter((t) => !t.name.includes('shadcn'));
|
|
|
| const parseSelectedTemplate = (llmOutput: string): { template: string; title: string } | null => {
|
| try {
|
|
|
| const templateNameMatch = llmOutput.match(/<templateName>(.*?)<\/templateName>/);
|
| const titleMatch = llmOutput.match(/<title>(.*?)<\/title>/);
|
|
|
| if (!templateNameMatch) {
|
| return null;
|
| }
|
|
|
| return { template: templateNameMatch[1].trim(), title: titleMatch?.[1].trim() || 'Untitled Project' };
|
| } catch (error) {
|
| console.error('Error parsing template selection:', error);
|
| return null;
|
| }
|
| };
|
|
|
| export const selectStarterTemplate = async (options: { message: string; model: string; provider: ProviderInfo }) => {
|
| const { message, model, provider } = options;
|
| const requestBody = {
|
| message,
|
| model,
|
| provider,
|
| system: starterTemplateSelectionPrompt(templates),
|
| };
|
| const response = await fetch('/api/llmcall', {
|
| method: 'POST',
|
| body: JSON.stringify(requestBody),
|
| });
|
| const respJson: { text: string } = await response.json();
|
| console.log(respJson);
|
|
|
| const { text } = respJson;
|
| const selectedTemplate = parseSelectedTemplate(text);
|
|
|
| if (selectedTemplate) {
|
| return selectedTemplate;
|
| } else {
|
| console.log('No template selected, using blank template');
|
|
|
| return {
|
| template: 'blank',
|
| title: '',
|
| };
|
| }
|
| };
|
|
|
| const getGitHubRepoContent = async (
|
| repoName: string,
|
| path: string = '',
|
| ): Promise<{ name: string; path: string; content: string }[]> => {
|
| const baseUrl = 'https://api.github.com';
|
|
|
| try {
|
| const token = Cookies.get('githubToken') || import.meta.env.VITE_GITHUB_ACCESS_TOKEN;
|
|
|
| const headers: HeadersInit = {
|
| Accept: 'application/vnd.github.v3+json',
|
| };
|
|
|
|
|
| if (token) {
|
| headers.Authorization = 'token ' + token;
|
| }
|
|
|
|
|
| const response = await fetch(`${baseUrl}/repos/${repoName}/contents/${path}`, {
|
| headers,
|
| });
|
|
|
| if (!response.ok) {
|
| throw new Error(`HTTP error! status: ${response.status}`);
|
| }
|
|
|
| const data: any = await response.json();
|
|
|
|
|
| if (!Array.isArray(data)) {
|
| if (data.type === 'file') {
|
|
|
| const content = atob(data.content);
|
| return [
|
| {
|
| name: data.name,
|
| path: data.path,
|
| content,
|
| },
|
| ];
|
| }
|
| }
|
|
|
|
|
| const contents = await Promise.all(
|
| data.map(async (item: any) => {
|
| if (item.type === 'dir') {
|
|
|
| return await getGitHubRepoContent(repoName, item.path);
|
| } else if (item.type === 'file') {
|
|
|
| const fileResponse = await fetch(item.url, {
|
| headers,
|
| });
|
| const fileData: any = await fileResponse.json();
|
| const content = atob(fileData.content);
|
|
|
| return [
|
| {
|
| name: item.name,
|
| path: item.path,
|
| content,
|
| },
|
| ];
|
| }
|
|
|
| return [];
|
| }),
|
| );
|
|
|
|
|
| return contents.flat();
|
| } catch (error) {
|
| console.error('Error fetching repo contents:', error);
|
| throw error;
|
| }
|
| };
|
|
|
| export async function getTemplates(templateName: string, title?: string) {
|
| const template = STARTER_TEMPLATES.find((t) => t.name == templateName);
|
|
|
| if (!template) {
|
| return null;
|
| }
|
|
|
| const githubRepo = template.githubRepo;
|
| const files = await getGitHubRepoContent(githubRepo);
|
|
|
| let filteredFiles = files;
|
|
|
| |
| |
| |
|
|
| filteredFiles = filteredFiles.filter((x) => x.path.startsWith('.git') == false);
|
|
|
|
|
| const comminLockFiles = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml'];
|
| filteredFiles = filteredFiles.filter((x) => comminLockFiles.includes(x.name) == false);
|
|
|
|
|
| filteredFiles = filteredFiles.filter((x) => x.path.startsWith('.bolt') == false);
|
|
|
|
|
| const templateIgnoreFile = files.find((x) => x.path.startsWith('.bolt') && x.name == 'ignore');
|
|
|
| const filesToImport = {
|
| files: filteredFiles,
|
| ignoreFile: [] as typeof filteredFiles,
|
| };
|
|
|
| if (templateIgnoreFile) {
|
|
|
| const ignorepatterns = templateIgnoreFile.content.split('\n').map((x) => x.trim());
|
| const ig = ignore().add(ignorepatterns);
|
|
|
|
|
| const ignoredFiles = filteredFiles.filter((x) => ig.ignores(x.path));
|
|
|
| filesToImport.files = filteredFiles;
|
| filesToImport.ignoreFile = ignoredFiles;
|
| }
|
|
|
| const assistantMessage = `
|
| <boltArtifact id="imported-files" title="${title || 'Importing Starter Files'}" type="bundled">
|
| ${filesToImport.files
|
| .map(
|
| (file) =>
|
| `<boltAction type="file" filePath="${file.path}">
|
| ${file.content}
|
| </boltAction>`,
|
| )
|
| .join('\n')}
|
| </boltArtifact>
|
| `;
|
| let userMessage = ``;
|
| const templatePromptFile = files.filter((x) => x.path.startsWith('.bolt')).find((x) => x.name == 'prompt');
|
|
|
| if (templatePromptFile) {
|
| userMessage = `
|
| TEMPLATE INSTRUCTIONS:
|
| ${templatePromptFile.content}
|
|
|
| IMPORTANT: Dont Forget to install the dependencies before running the app
|
| ---
|
| `;
|
| }
|
|
|
| if (filesToImport.ignoreFile.length > 0) {
|
| userMessage =
|
| userMessage +
|
| `
|
| STRICT FILE ACCESS RULES - READ CAREFULLY:
|
|
|
| The following files are READ-ONLY and must never be modified:
|
| ${filesToImport.ignoreFile.map((file) => `- ${file.path}`).join('\n')}
|
|
|
| Permitted actions:
|
| ✓ Import these files as dependencies
|
| ✓ Read from these files
|
| ✓ Reference these files
|
|
|
| Strictly forbidden actions:
|
| ❌ Modify any content within these files
|
| ❌ Delete these files
|
| ❌ Rename these files
|
| ❌ Move these files
|
| ❌ Create new versions of these files
|
| ❌ Suggest changes to these files
|
|
|
| Any attempt to modify these protected files will result in immediate termination of the operation.
|
|
|
| If you need to make changes to functionality, create new files instead of modifying the protected ones listed above.
|
| ---
|
| `;
|
| }
|
|
|
| userMessage += `
|
| ---
|
| template import is done, and you can now use the imported files,
|
| edit only the files that need to be changed, and you can create new files as needed.
|
| NO NOT EDIT/WRITE ANY FILES THAT ALREADY EXIST IN THE PROJECT AND DOES NOT NEED TO BE MODIFIED
|
| ---
|
| Now that the Template is imported please continue with my original request
|
| `;
|
|
|
| return {
|
| assistantMessage,
|
| userMessage,
|
| };
|
| }
|
|
|