Spaces:
Build error
Build error
| 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 { | |
| // Extract content between <templateName> tags | |
| 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', | |
| }; | |
| // Add your GitHub token if needed | |
| if (token) { | |
| headers.Authorization = 'token ' + token; | |
| } | |
| // Fetch contents of the path | |
| 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 it's a single file, return its content | |
| if (!Array.isArray(data)) { | |
| if (data.type === 'file') { | |
| // If it's a file, get its content | |
| const content = atob(data.content); // Decode base64 content | |
| return [ | |
| { | |
| name: data.name, | |
| path: data.path, | |
| content, | |
| }, | |
| ]; | |
| } | |
| } | |
| // Process directory contents recursively | |
| const contents = await Promise.all( | |
| data.map(async (item: any) => { | |
| if (item.type === 'dir') { | |
| // Recursively get contents of subdirectories | |
| return await getGitHubRepoContent(repoName, item.path); | |
| } else if (item.type === 'file') { | |
| // Fetch file content | |
| const fileResponse = await fetch(item.url, { | |
| headers, | |
| }); | |
| const fileData: any = await fileResponse.json(); | |
| const content = atob(fileData.content); // Decode base64 content | |
| return [ | |
| { | |
| name: item.name, | |
| path: item.path, | |
| content, | |
| }, | |
| ]; | |
| } | |
| return []; | |
| }), | |
| ); | |
| // Flatten the array of contents | |
| 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; | |
| /* | |
| * ignoring common unwanted files | |
| * exclude .git | |
| */ | |
| filteredFiles = filteredFiles.filter((x) => x.path.startsWith('.git') == false); | |
| // exclude lock files | |
| const comminLockFiles = ['package-lock.json', 'yarn.lock', 'pnpm-lock.yaml']; | |
| filteredFiles = filteredFiles.filter((x) => comminLockFiles.includes(x.name) == false); | |
| // exclude .bolt | |
| filteredFiles = filteredFiles.filter((x) => x.path.startsWith('.bolt') == false); | |
| // check for ignore file in .bolt folder | |
| const templateIgnoreFile = files.find((x) => x.path.startsWith('.bolt') && x.name == 'ignore'); | |
| const filesToImport = { | |
| files: filteredFiles, | |
| ignoreFile: [] as typeof filteredFiles, | |
| }; | |
| if (templateIgnoreFile) { | |
| // redacting files specified in ignore file | |
| const ignorepatterns = templateIgnoreFile.content.split('\n').map((x) => x.trim()); | |
| const ig = ignore().add(ignorepatterns); | |
| // filteredFiles = filteredFiles.filter(x => !ig.ignores(x.path)) | |
| 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, | |
| }; | |
| } | |