| 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. |
| MOST IMPORTANT: YOU DONT HAVE TIME TO THINK JUST START RESPONDING BASED ON HUNCH |
| `; |
|
|
| 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, |
| }; |
| } |
|
|