Reuse automatic setup commands for git import
Browse files
app/components/chat/GitCloneButton.tsx
CHANGED
|
@@ -2,6 +2,8 @@ import ignore from 'ignore';
|
|
| 2 |
import { useGit } from '~/lib/hooks/useGit';
|
| 3 |
import type { Message } from 'ai';
|
| 4 |
import WithTooltip from '~/components/ui/Tooltip';
|
|
|
|
|
|
|
| 5 |
|
| 6 |
const IGNORE_PATTERNS = [
|
| 7 |
'node_modules/**',
|
|
@@ -28,7 +30,6 @@ const IGNORE_PATTERNS = [
|
|
| 28 |
];
|
| 29 |
|
| 30 |
const ig = ignore().add(IGNORE_PATTERNS);
|
| 31 |
-
const generateId = () => Math.random().toString(36).substring(2, 15);
|
| 32 |
|
| 33 |
interface GitCloneButtonProps {
|
| 34 |
className?: string;
|
|
@@ -52,36 +53,47 @@ export default function GitCloneButton({ importChat }: GitCloneButtonProps) {
|
|
| 52 |
console.log(filePaths);
|
| 53 |
|
| 54 |
const textDecoder = new TextDecoder('utf-8');
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
role: 'assistant',
|
| 57 |
content: `Cloning the repo ${repoUrl} into ${workdir}
|
| 58 |
-
<boltArtifact id="imported-files" title="Git Cloned Files" type="bundled"
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
return `<boltAction type="file" filePath="${filePath}">
|
| 69 |
-
${textDecoder.decode(content)}
|
| 70 |
-
</boltAction>`;
|
| 71 |
-
} else {
|
| 72 |
-
return '';
|
| 73 |
-
}
|
| 74 |
-
})
|
| 75 |
-
.join('\n')}
|
| 76 |
-
</boltArtifact>`,
|
| 77 |
id: generateId(),
|
| 78 |
createdAt: new Date(),
|
| 79 |
};
|
| 80 |
-
console.log(JSON.stringify(message));
|
| 81 |
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
-
|
| 85 |
}
|
| 86 |
}
|
| 87 |
};
|
|
|
|
| 2 |
import { useGit } from '~/lib/hooks/useGit';
|
| 3 |
import type { Message } from 'ai';
|
| 4 |
import WithTooltip from '~/components/ui/Tooltip';
|
| 5 |
+
import { detectProjectCommands, createCommandsMessage } from '~/utils/projectCommands';
|
| 6 |
+
import { generateId } from '~/utils/fileUtils';
|
| 7 |
|
| 8 |
const IGNORE_PATTERNS = [
|
| 9 |
'node_modules/**',
|
|
|
|
| 30 |
];
|
| 31 |
|
| 32 |
const ig = ignore().add(IGNORE_PATTERNS);
|
|
|
|
| 33 |
|
| 34 |
interface GitCloneButtonProps {
|
| 35 |
className?: string;
|
|
|
|
| 53 |
console.log(filePaths);
|
| 54 |
|
| 55 |
const textDecoder = new TextDecoder('utf-8');
|
| 56 |
+
|
| 57 |
+
// Convert files to common format for command detection
|
| 58 |
+
const fileContents = filePaths
|
| 59 |
+
.map((filePath) => {
|
| 60 |
+
const { data: content, encoding } = data[filePath];
|
| 61 |
+
return {
|
| 62 |
+
path: filePath,
|
| 63 |
+
content: encoding === 'utf8' ? content : content instanceof Uint8Array ? textDecoder.decode(content) : '',
|
| 64 |
+
};
|
| 65 |
+
})
|
| 66 |
+
.filter((f) => f.content);
|
| 67 |
+
|
| 68 |
+
// Detect and create commands message
|
| 69 |
+
const commands = await detectProjectCommands(fileContents);
|
| 70 |
+
const commandsMessage = createCommandsMessage(commands);
|
| 71 |
+
|
| 72 |
+
// Create files message
|
| 73 |
+
const filesMessage: Message = {
|
| 74 |
role: 'assistant',
|
| 75 |
content: `Cloning the repo ${repoUrl} into ${workdir}
|
| 76 |
+
<boltArtifact id="imported-files" title="Git Cloned Files" type="bundled">
|
| 77 |
+
${fileContents
|
| 78 |
+
.map(
|
| 79 |
+
(file) =>
|
| 80 |
+
`<boltAction type="file" filePath="${file.path}">
|
| 81 |
+
${file.content}
|
| 82 |
+
</boltAction>`,
|
| 83 |
+
)
|
| 84 |
+
.join('\n')}
|
| 85 |
+
</boltArtifact>`,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
id: generateId(),
|
| 87 |
createdAt: new Date(),
|
| 88 |
};
|
|
|
|
| 89 |
|
| 90 |
+
const messages = [filesMessage];
|
| 91 |
+
|
| 92 |
+
if (commandsMessage) {
|
| 93 |
+
messages.push(commandsMessage);
|
| 94 |
+
}
|
| 95 |
|
| 96 |
+
await importChat(`Git Project:${repoUrl.split('/').slice(-1)[0]}`, messages);
|
| 97 |
}
|
| 98 |
}
|
| 99 |
};
|
app/components/chat/ImportFolderButton.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
import React, { useState } from 'react';
|
| 2 |
import type { Message } from 'ai';
|
| 3 |
import { toast } from 'react-toastify';
|
| 4 |
-
import { MAX_FILES, isBinaryFile, shouldIncludeFile } from '
|
| 5 |
-
import { createChatFromFolder } from '
|
| 6 |
|
| 7 |
interface ImportFolderButtonProps {
|
| 8 |
className?: string;
|
|
@@ -17,12 +17,14 @@ export const ImportFolderButton: React.FC<ImportFolderButtonProps> = ({ classNam
|
|
| 17 |
|
| 18 |
if (allFiles.length > MAX_FILES) {
|
| 19 |
toast.error(
|
| 20 |
-
`This folder contains ${allFiles.length.toLocaleString()} files. This product is not yet optimized for very large projects. Please select a folder with fewer than ${MAX_FILES.toLocaleString()} files
|
| 21 |
);
|
| 22 |
return;
|
| 23 |
}
|
|
|
|
| 24 |
const folderName = allFiles[0]?.webkitRelativePath.split('/')[0] || 'Unknown Folder';
|
| 25 |
setIsLoading(true);
|
|
|
|
| 26 |
const loadingToast = toast.loading(`Importing ${folderName}...`);
|
| 27 |
|
| 28 |
try {
|
|
|
|
| 1 |
import React, { useState } from 'react';
|
| 2 |
import type { Message } from 'ai';
|
| 3 |
import { toast } from 'react-toastify';
|
| 4 |
+
import { MAX_FILES, isBinaryFile, shouldIncludeFile } from '~/utils/fileUtils';
|
| 5 |
+
import { createChatFromFolder } from '~/utils/folderImport';
|
| 6 |
|
| 7 |
interface ImportFolderButtonProps {
|
| 8 |
className?: string;
|
|
|
|
| 17 |
|
| 18 |
if (allFiles.length > MAX_FILES) {
|
| 19 |
toast.error(
|
| 20 |
+
`This folder contains ${allFiles.length.toLocaleString()} files. This product is not yet optimized for very large projects. Please select a folder with fewer than ${MAX_FILES.toLocaleString()} files.`,
|
| 21 |
);
|
| 22 |
return;
|
| 23 |
}
|
| 24 |
+
|
| 25 |
const folderName = allFiles[0]?.webkitRelativePath.split('/')[0] || 'Unknown Folder';
|
| 26 |
setIsLoading(true);
|
| 27 |
+
|
| 28 |
const loadingToast = toast.loading(`Importing ${folderName}...`);
|
| 29 |
|
| 30 |
try {
|
app/components/chat/SendButton.client.tsx
CHANGED
|
@@ -23,6 +23,7 @@ export const SendButton = ({ show, isStreaming, disabled, onClick }: SendButtonP
|
|
| 23 |
disabled={disabled}
|
| 24 |
onClick={(event) => {
|
| 25 |
event.preventDefault();
|
|
|
|
| 26 |
if (!disabled) {
|
| 27 |
onClick?.(event);
|
| 28 |
}
|
|
|
|
| 23 |
disabled={disabled}
|
| 24 |
onClick={(event) => {
|
| 25 |
event.preventDefault();
|
| 26 |
+
|
| 27 |
if (!disabled) {
|
| 28 |
onClick?.(event);
|
| 29 |
}
|
app/utils/fileUtils.ts
CHANGED
|
@@ -29,10 +29,12 @@ export const isBinaryFile = async (file: File): Promise<boolean> => {
|
|
| 29 |
|
| 30 |
for (let i = 0; i < buffer.length; i++) {
|
| 31 |
const byte = buffer[i];
|
|
|
|
| 32 |
if (byte === 0 || (byte < 32 && byte !== 9 && byte !== 10 && byte !== 13)) {
|
| 33 |
return true;
|
| 34 |
}
|
| 35 |
}
|
|
|
|
| 36 |
return false;
|
| 37 |
};
|
| 38 |
|
|
@@ -41,8 +43,11 @@ export const shouldIncludeFile = (path: string): boolean => {
|
|
| 41 |
};
|
| 42 |
|
| 43 |
const readPackageJson = async (files: File[]): Promise<{ scripts?: Record<string, string> } | null> => {
|
| 44 |
-
const packageJsonFile = files.find(f => f.webkitRelativePath.endsWith('package.json'));
|
| 45 |
-
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
try {
|
| 48 |
const content = await new Promise<string>((resolve, reject) => {
|
|
@@ -59,29 +64,32 @@ const readPackageJson = async (files: File[]): Promise<{ scripts?: Record<string
|
|
| 59 |
}
|
| 60 |
};
|
| 61 |
|
| 62 |
-
export const detectProjectType = async (
|
| 63 |
-
|
|
|
|
|
|
|
| 64 |
|
| 65 |
if (hasFile('package.json')) {
|
| 66 |
const packageJson = await readPackageJson(files);
|
| 67 |
const scripts = packageJson?.scripts || {};
|
| 68 |
-
|
| 69 |
// Check for preferred commands in priority order
|
| 70 |
const preferredCommands = ['dev', 'start', 'preview'];
|
| 71 |
-
const availableCommand = preferredCommands.find(cmd => scripts[cmd]);
|
| 72 |
-
|
| 73 |
if (availableCommand) {
|
| 74 |
return {
|
| 75 |
type: 'Node.js',
|
| 76 |
setupCommand: `npm install && npm run ${availableCommand}`,
|
| 77 |
-
followupMessage: `Found "${availableCommand}" script in package.json. Running "npm run ${availableCommand}" after installation
|
| 78 |
};
|
| 79 |
}
|
| 80 |
|
| 81 |
return {
|
| 82 |
type: 'Node.js',
|
| 83 |
setupCommand: 'npm install',
|
| 84 |
-
followupMessage:
|
|
|
|
| 85 |
};
|
| 86 |
}
|
| 87 |
|
|
@@ -89,7 +97,7 @@ export const detectProjectType = async (files: File[]): Promise<{ type: string;
|
|
| 89 |
return {
|
| 90 |
type: 'Static',
|
| 91 |
setupCommand: 'npx --yes serve',
|
| 92 |
-
followupMessage: ''
|
| 93 |
};
|
| 94 |
}
|
| 95 |
|
|
|
|
| 29 |
|
| 30 |
for (let i = 0; i < buffer.length; i++) {
|
| 31 |
const byte = buffer[i];
|
| 32 |
+
|
| 33 |
if (byte === 0 || (byte < 32 && byte !== 9 && byte !== 10 && byte !== 13)) {
|
| 34 |
return true;
|
| 35 |
}
|
| 36 |
}
|
| 37 |
+
|
| 38 |
return false;
|
| 39 |
};
|
| 40 |
|
|
|
|
| 43 |
};
|
| 44 |
|
| 45 |
const readPackageJson = async (files: File[]): Promise<{ scripts?: Record<string, string> } | null> => {
|
| 46 |
+
const packageJsonFile = files.find((f) => f.webkitRelativePath.endsWith('package.json'));
|
| 47 |
+
|
| 48 |
+
if (!packageJsonFile) {
|
| 49 |
+
return null;
|
| 50 |
+
}
|
| 51 |
|
| 52 |
try {
|
| 53 |
const content = await new Promise<string>((resolve, reject) => {
|
|
|
|
| 64 |
}
|
| 65 |
};
|
| 66 |
|
| 67 |
+
export const detectProjectType = async (
|
| 68 |
+
files: File[],
|
| 69 |
+
): Promise<{ type: string; setupCommand: string; followupMessage: string }> => {
|
| 70 |
+
const hasFile = (name: string) => files.some((f) => f.webkitRelativePath.endsWith(name));
|
| 71 |
|
| 72 |
if (hasFile('package.json')) {
|
| 73 |
const packageJson = await readPackageJson(files);
|
| 74 |
const scripts = packageJson?.scripts || {};
|
| 75 |
+
|
| 76 |
// Check for preferred commands in priority order
|
| 77 |
const preferredCommands = ['dev', 'start', 'preview'];
|
| 78 |
+
const availableCommand = preferredCommands.find((cmd) => scripts[cmd]);
|
| 79 |
+
|
| 80 |
if (availableCommand) {
|
| 81 |
return {
|
| 82 |
type: 'Node.js',
|
| 83 |
setupCommand: `npm install && npm run ${availableCommand}`,
|
| 84 |
+
followupMessage: `Found "${availableCommand}" script in package.json. Running "npm run ${availableCommand}" after installation.`,
|
| 85 |
};
|
| 86 |
}
|
| 87 |
|
| 88 |
return {
|
| 89 |
type: 'Node.js',
|
| 90 |
setupCommand: 'npm install',
|
| 91 |
+
followupMessage:
|
| 92 |
+
'Would you like me to inspect package.json to determine the available scripts for running this project?',
|
| 93 |
};
|
| 94 |
}
|
| 95 |
|
|
|
|
| 97 |
return {
|
| 98 |
type: 'Static',
|
| 99 |
setupCommand: 'npx --yes serve',
|
| 100 |
+
followupMessage: '',
|
| 101 |
};
|
| 102 |
}
|
| 103 |
|
app/utils/folderImport.ts
CHANGED
|
@@ -1,23 +1,24 @@
|
|
| 1 |
import type { Message } from 'ai';
|
| 2 |
-
import { generateId
|
|
|
|
| 3 |
|
| 4 |
export const createChatFromFolder = async (
|
| 5 |
files: File[],
|
| 6 |
binaryFiles: string[],
|
| 7 |
-
folderName: string
|
| 8 |
): Promise<Message[]> => {
|
| 9 |
const fileArtifacts = await Promise.all(
|
| 10 |
files.map(async (file) => {
|
| 11 |
-
return new Promise<string>((resolve, reject) => {
|
| 12 |
const reader = new FileReader();
|
|
|
|
| 13 |
reader.onload = () => {
|
| 14 |
const content = reader.result as string;
|
| 15 |
const relativePath = file.webkitRelativePath.split('/').slice(1).join('/');
|
| 16 |
-
resolve(
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
);
|
| 21 |
};
|
| 22 |
reader.onerror = reject;
|
| 23 |
reader.readAsText(file);
|
|
@@ -25,32 +26,30 @@ ${content}
|
|
| 25 |
}),
|
| 26 |
);
|
| 27 |
|
| 28 |
-
const
|
| 29 |
-
const
|
| 30 |
-
const followupMessage = project.followupMessage ? `\n\n${project.followupMessage}` : '';
|
| 31 |
|
| 32 |
-
const binaryFilesMessage =
|
| 33 |
-
|
| 34 |
-
|
|
|
|
| 35 |
|
| 36 |
-
const
|
| 37 |
role: 'assistant',
|
| 38 |
content: `I've imported the contents of the "${folderName}" folder.${binaryFilesMessage}
|
| 39 |
|
| 40 |
<boltArtifact id="imported-files" title="Imported Files">
|
| 41 |
-
${fileArtifacts
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
</boltArtifact>`,
|
| 43 |
id: generateId(),
|
| 44 |
createdAt: new Date(),
|
| 45 |
-
}
|
| 46 |
-
role: 'assistant',
|
| 47 |
-
content: `
|
| 48 |
-
<boltArtifact id="imported-files" title="Imported Files">
|
| 49 |
-
${setupCommand}
|
| 50 |
-
</boltArtifact>${followupMessage}`,
|
| 51 |
-
id: generateId(),
|
| 52 |
-
createdAt: new Date(),
|
| 53 |
-
}];
|
| 54 |
|
| 55 |
const userMessage: Message = {
|
| 56 |
role: 'user',
|
|
@@ -59,5 +58,11 @@ ${setupCommand}
|
|
| 59 |
createdAt: new Date(),
|
| 60 |
};
|
| 61 |
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
};
|
|
|
|
| 1 |
import type { Message } from 'ai';
|
| 2 |
+
import { generateId } from './fileUtils';
|
| 3 |
+
import { detectProjectCommands, createCommandsMessage } from './projectCommands';
|
| 4 |
|
| 5 |
export const createChatFromFolder = async (
|
| 6 |
files: File[],
|
| 7 |
binaryFiles: string[],
|
| 8 |
+
folderName: string,
|
| 9 |
): Promise<Message[]> => {
|
| 10 |
const fileArtifacts = await Promise.all(
|
| 11 |
files.map(async (file) => {
|
| 12 |
+
return new Promise<{ content: string; path: string }>((resolve, reject) => {
|
| 13 |
const reader = new FileReader();
|
| 14 |
+
|
| 15 |
reader.onload = () => {
|
| 16 |
const content = reader.result as string;
|
| 17 |
const relativePath = file.webkitRelativePath.split('/').slice(1).join('/');
|
| 18 |
+
resolve({
|
| 19 |
+
content,
|
| 20 |
+
path: relativePath,
|
| 21 |
+
});
|
|
|
|
| 22 |
};
|
| 23 |
reader.onerror = reject;
|
| 24 |
reader.readAsText(file);
|
|
|
|
| 26 |
}),
|
| 27 |
);
|
| 28 |
|
| 29 |
+
const commands = await detectProjectCommands(fileArtifacts);
|
| 30 |
+
const commandsMessage = createCommandsMessage(commands);
|
|
|
|
| 31 |
|
| 32 |
+
const binaryFilesMessage =
|
| 33 |
+
binaryFiles.length > 0
|
| 34 |
+
? `\n\nSkipped ${binaryFiles.length} binary files:\n${binaryFiles.map((f) => `- ${f}`).join('\n')}`
|
| 35 |
+
: '';
|
| 36 |
|
| 37 |
+
const filesMessage: Message = {
|
| 38 |
role: 'assistant',
|
| 39 |
content: `I've imported the contents of the "${folderName}" folder.${binaryFilesMessage}
|
| 40 |
|
| 41 |
<boltArtifact id="imported-files" title="Imported Files">
|
| 42 |
+
${fileArtifacts
|
| 43 |
+
.map(
|
| 44 |
+
(file) => `<boltAction type="file" filePath="${file.path}">
|
| 45 |
+
${file.content}
|
| 46 |
+
</boltAction>`,
|
| 47 |
+
)
|
| 48 |
+
.join('\n\n')}
|
| 49 |
</boltArtifact>`,
|
| 50 |
id: generateId(),
|
| 51 |
createdAt: new Date(),
|
| 52 |
+
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
const userMessage: Message = {
|
| 55 |
role: 'user',
|
|
|
|
| 58 |
createdAt: new Date(),
|
| 59 |
};
|
| 60 |
|
| 61 |
+
const messages = [userMessage, filesMessage];
|
| 62 |
+
|
| 63 |
+
if (commandsMessage) {
|
| 64 |
+
messages.push(commandsMessage);
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
return messages;
|
| 68 |
};
|
app/utils/projectCommands.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { Message } from 'ai';
|
| 2 |
+
import { generateId } from './fileUtils';
|
| 3 |
+
|
| 4 |
+
export interface ProjectCommands {
|
| 5 |
+
type: string;
|
| 6 |
+
setupCommand: string;
|
| 7 |
+
followupMessage: string;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
interface FileContent {
|
| 11 |
+
content: string;
|
| 12 |
+
path: string;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
export async function detectProjectCommands(files: FileContent[]): Promise<ProjectCommands> {
|
| 16 |
+
const hasFile = (name: string) => files.some((f) => f.path.endsWith(name));
|
| 17 |
+
|
| 18 |
+
if (hasFile('package.json')) {
|
| 19 |
+
const packageJsonFile = files.find((f) => f.path.endsWith('package.json'));
|
| 20 |
+
|
| 21 |
+
if (!packageJsonFile) {
|
| 22 |
+
return { type: '', setupCommand: '', followupMessage: '' };
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
try {
|
| 26 |
+
const packageJson = JSON.parse(packageJsonFile.content);
|
| 27 |
+
const scripts = packageJson?.scripts || {};
|
| 28 |
+
|
| 29 |
+
// Check for preferred commands in priority order
|
| 30 |
+
const preferredCommands = ['dev', 'start', 'preview'];
|
| 31 |
+
const availableCommand = preferredCommands.find((cmd) => scripts[cmd]);
|
| 32 |
+
|
| 33 |
+
if (availableCommand) {
|
| 34 |
+
return {
|
| 35 |
+
type: 'Node.js',
|
| 36 |
+
setupCommand: `npm install && npm run ${availableCommand}`,
|
| 37 |
+
followupMessage: `Found "${availableCommand}" script in package.json. Running "npm run ${availableCommand}" after installation.`,
|
| 38 |
+
};
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
return {
|
| 42 |
+
type: 'Node.js',
|
| 43 |
+
setupCommand: 'npm install',
|
| 44 |
+
followupMessage:
|
| 45 |
+
'Would you like me to inspect package.json to determine the available scripts for running this project?',
|
| 46 |
+
};
|
| 47 |
+
} catch (error) {
|
| 48 |
+
console.error('Error parsing package.json:', error);
|
| 49 |
+
return { type: '', setupCommand: '', followupMessage: '' };
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
if (hasFile('index.html')) {
|
| 54 |
+
return {
|
| 55 |
+
type: 'Static',
|
| 56 |
+
setupCommand: 'npx --yes serve',
|
| 57 |
+
followupMessage: '',
|
| 58 |
+
};
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
return { type: '', setupCommand: '', followupMessage: '' };
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
export function createCommandsMessage(commands: ProjectCommands): Message | null {
|
| 65 |
+
if (!commands.setupCommand) {
|
| 66 |
+
return null;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
return {
|
| 70 |
+
role: 'assistant',
|
| 71 |
+
content: `
|
| 72 |
+
<boltArtifact id="project-setup" title="Project Setup">
|
| 73 |
+
<boltAction type="shell">
|
| 74 |
+
${commands.setupCommand}
|
| 75 |
+
</boltAction>
|
| 76 |
+
</boltArtifact>${commands.followupMessage ? `\n\n${commands.followupMessage}` : ''}`,
|
| 77 |
+
id: generateId(),
|
| 78 |
+
createdAt: new Date(),
|
| 79 |
+
};
|
| 80 |
+
}
|