CoolFace
Apppublic

enzostvs/deepsite

sourceHugging Facemitupdated 8mo agoView on Hugging Face
17klikes
format.ts336 linesDownload Raw Back to lib
1import {2  END_FILE_CONTENT,3  END_PROJECT_NAME,4  START_FILE_CONTENT,5  START_PROJECT_NAME,6  SEARCH_START,7  DIVIDER,8  REPLACE_END,9} from "./prompts";10import { File } from "./type";11 12// todo: the Editing stuffs in message doesnt show when it"s a SEARCH and REPLACE operation, I mean it shows it but only at the end of the message, not during the generation. fix that.13 14/**15 * Validates that a filename has an extension.16 * Returns the filename if valid, null otherwise.17 * Allows dotfiles (like .gitignore) and regular files with extensions (like app.py).18 */19const validateFilename = (filename: string): string | null => {20  if (!filename) return null;21 22  const trimmed = filename.trim();23  if (!trimmed) return null;24 25  // Find the last dot in the filename26  const lastDotIndex = trimmed.lastIndexOf(".");27 28  // No dot found - invalid (no extension)29  if (lastDotIndex === -1) return null;30 31  // Dot at the end - invalid (no extension after dot)32  if (lastDotIndex === trimmed.length - 1) return null;33 34  // Dot at the start - this is a dotfile (like .gitignore), which is valid35  // Dot in the middle - this is a regular file with extension (like app.py), which is valid36  // Both cases are acceptable as long as there's something after the dot37 38  return trimmed;39};40 41/**42 * Check if the end of a string contains a partial match of a target string43 */44const hasPartialMarkerAtEnd = (text: string, marker: string): number => {45  // Check for partial matches starting from length 3 (to avoid false positives with common substrings)46  for (47    let len = Math.max(3, Math.floor(marker.length / 2));48    len < marker.length;49    len++50  ) {51    const partialMarker = marker.substring(0, len);52    if (text.endsWith(partialMarker)) {53      return len; // Return the length of the partial match54    }55  }56  return 0; // No partial match found57};58 59/**60 * Extract message content by removing all special markers and their content61 */62const extractMessageContent = (message: string): string => {63  let result = message;64 65  // Remove all complete START_FILE_CONTENT...END_FILE_CONTENT blocks66  const fileContentRegex = new RegExp(67    `${START_FILE_CONTENT}[\\s\\S]*?${END_FILE_CONTENT}`,68    "g"69  );70  result = result.replace(fileContentRegex, "");71 72  // Remove incomplete START_FILE_CONTENT blocks (streaming - no END_FILE_CONTENT yet)73  const incompleteFileIndex = result.indexOf(START_FILE_CONTENT);74  if (incompleteFileIndex !== -1) {75    result = result.substring(0, incompleteFileIndex);76  }77 78  // Remove all complete START_PROJECT_NAME...END_PROJECT_NAME blocks79  const projectNameRegex = new RegExp(80    `${START_PROJECT_NAME}[\\s\\S]*?${END_PROJECT_NAME}`,81    "g"82  );83  result = result.replace(projectNameRegex, "");84 85  // Remove incomplete START_PROJECT_NAME blocks (streaming - no END_PROJECT_NAME yet)86  const incompleteProjectIndex = result.indexOf(START_PROJECT_NAME);87  if (incompleteProjectIndex !== -1) {88    result = result.substring(0, incompleteProjectIndex);89  }90 91  // Remove all complete SEARCH_START...REPLACE_END blocks92  const searchReplaceRegex = new RegExp(93    `${SEARCH_START}[\\s\\S]*?${REPLACE_END}`,94    "g"95  );96  result = result.replace(searchReplaceRegex, "");97 98  // Remove incomplete SEARCH_START blocks (streaming - no REPLACE_END yet)99  const incompleteSearchIndex = result.indexOf(SEARCH_START);100  if (incompleteSearchIndex !== -1) {101    result = result.substring(0, incompleteSearchIndex);102  }103 104  // Handle incomplete/partial markers at the end (for streaming)105  // This is critical to prevent showing marker text to users during streaming106  const markers = [START_FILE_CONTENT, START_PROJECT_NAME, SEARCH_START];107 108  for (const marker of markers) {109    const partialLength = hasPartialMarkerAtEnd(result, marker);110    if (partialLength > 0) {111      result = result.substring(0, result.length - partialLength);112      break; // Only one marker can be at the end113    }114  }115 116  // Clean up extra whitespace117  return result.trim();118};119 120export const formatResponse = (message: string, currentFiles: File[]) => {121  // Track which files have been created or modified122  const modifiedFiles = new Map<string, File>();123 124  // Extract message content (everything outside special markers)125  const messageContent = extractMessageContent(message);126 127  // Extract project title128  let projectTitle =129    message.split(START_PROJECT_NAME)[1]?.split(END_PROJECT_NAME)[0]?.trim() ??130    "";131 132  // Handle partial project title markers133  if (projectTitle && !message.includes(END_PROJECT_NAME)) {134    for (let i = END_PROJECT_NAME.length - 1; i > 0; i--) {135      const partialMarker = END_PROJECT_NAME.substring(0, i);136      if (projectTitle.endsWith(partialMarker)) {137        projectTitle = projectTitle138          .substring(0, projectTitle.length - partialMarker.length)139          .trim();140        break;141      }142    }143  }144 145  if (message.includes(SEARCH_START)) {146    const searchSections = message.split(SEARCH_START).slice(1);147 148    for (149      let sectionIndex = 0;150      sectionIndex < searchSections.length;151      sectionIndex++152    ) {153      const section = searchSections[sectionIndex];154      const isLastSection = sectionIndex === searchSections.length - 1;155 156      // Check if this section has a complete REPLACE_END marker157      const hasReplaceEnd = section.includes(REPLACE_END);158 159      // For incomplete sections (last section without REPLACE_END), skip processing160      // to avoid applying partial replacements that corrupt the file161      if (isLastSection && !hasReplaceEnd) {162        continue;163      }164 165      const searchPart = section.split(REPLACE_END)[0];166      if (!searchPart) continue;167 168      const dividerIndex = searchPart.indexOf(DIVIDER);169      if (dividerIndex === -1) continue;170 171      const searchContent = searchPart.substring(0, dividerIndex);172      const replaceContent = searchPart.substring(173        dividerIndex + DIVIDER.length174      );175 176      if (!searchContent || !replaceContent) continue;177 178      // Extract filename from the first line (same line as SEARCH_START)179      const firstNewlineIndex = searchContent.indexOf("\n");180      let filename = "";181      let searchContentAfterFilename = searchContent;182 183      if (firstNewlineIndex !== -1) {184        // Filename is on the first line (same line as SEARCH_START marker)185        filename = searchContent.substring(0, firstNewlineIndex).trim();186        searchContentAfterFilename = searchContent.substring(187          firstNewlineIndex + 1188        );189      } else {190        // No newline found, entire content might be just the filename191        filename = searchContent.trim();192        searchContentAfterFilename = "";193      }194 195      // Validate filename has extension196      const validatedFilename = validateFilename(filename);197      if (!validatedFilename) continue;198      filename = validatedFilename;199 200      const searchCodeStart = searchContentAfterFilename.indexOf("```");201      if (searchCodeStart === -1) continue;202 203      const searchCodeBlock =204        searchContentAfterFilename.substring(searchCodeStart);205      let searchMatch = searchCodeBlock.match(/^```[\w]*\n?([\s\S]*?)```/);206      if (!searchMatch) {207        searchMatch = searchCodeBlock.match(/^```[\w]*\n?([\s\S]*)/);208      }209      if (!searchMatch) continue;210 211      const searchText = searchMatch[1].trim();212 213      const replaceCodeStart = replaceContent.indexOf("```");214      if (replaceCodeStart === -1) continue;215 216      const replaceCodeBlock = replaceContent.substring(replaceCodeStart);217      let replaceMatch = replaceCodeBlock.match(/^```[\w]*\n?([\s\S]*?)```/);218      if (!replaceMatch) {219        replaceMatch = replaceCodeBlock.match(/^```[\w]*\n?([\s\S]*)/);220      }221      if (!replaceMatch) continue;222 223      let replaceText = replaceMatch[1];224      for (let i = 1; i < 3; i++) {225        const partialClosing = "`".repeat(i);226        if (replaceText.endsWith(partialClosing)) {227          replaceText = replaceText.substring(0, replaceText.length - i);228          break;229        }230      }231      replaceText = replaceText.trim();232 233      // First check if we already have a modified version of this file in the current operation234      const existingModifiedFile = modifiedFiles.get(filename);235      const fileToModify =236        existingModifiedFile || currentFiles.find((f) => f.path === filename);237 238      if (fileToModify && fileToModify.content) {239        const newContent = fileToModify.content.replace(240          searchText,241          replaceText242        );243        if (newContent !== fileToModify.content) {244          modifiedFiles.set(filename, { path: filename, content: newContent });245        }246      } else if (!fileToModify) {247        modifiedFiles.set(filename, { path: filename, content: replaceText });248      }249    }250  }251 252  // Handle new file creation blocks253  if (message.includes(START_FILE_CONTENT)) {254    const fileSections = message.split(START_FILE_CONTENT).slice(1);255 256    for (const section of fileSections) {257      const rawContent = section.split(END_FILE_CONTENT)[0] || "";258 259      // Extract filename - it's on the same line as START_FILE_CONTENT (before first newline)260      const firstNewlineIndex = rawContent.indexOf("\n");261      let path = "";262      let contentAfterFilename = rawContent;263 264      if (firstNewlineIndex !== -1) {265        // Filename is before the first newline (same line as START_FILE_CONTENT marker)266        path = rawContent.substring(0, firstNewlineIndex).trim();267        contentAfterFilename = rawContent.substring(firstNewlineIndex + 1);268      } else {269        // No newline found, entire content might be just the filename270        path = rawContent.trim();271        contentAfterFilename = "";272      }273 274      // Validate that path has an extension275      const validatedPath = validateFilename(path);276      if (!validatedPath) continue;277      path = validatedPath;278 279      // Find code block280      const codeBlockStart = contentAfterFilename.indexOf("```");281 282      if (codeBlockStart === -1) {283        // No code block, use raw content after filename284        const content = contentAfterFilename.trim();285        if (content || !currentFiles.find((f) => f.path === path)) {286          // Always add files from the message287          modifiedFiles.set(path, { path, content });288        }289      } else {290        // Has code block - extract content between ``` markers291        const afterCodeBlockStart =292          contentAfterFilename.substring(codeBlockStart);293 294        // Try to match complete code block first295        const completeMatch = afterCodeBlockStart.match(296          /^```[\w]*\n?([\s\S]*?)```/297        );298 299        if (completeMatch) {300          const content = completeMatch[1].trim();301          modifiedFiles.set(path, { path, content });302        } else {303          // Incomplete code block (streaming) - match everything after opening ```304          const incompleteMatch = afterCodeBlockStart.match(305            /^```[\w]*\n?([\s\S]*)/306          );307          if (incompleteMatch) {308            let content = incompleteMatch[1];309            // Remove trailing partial closing marker if present310            for (let i = 1; i < 3; i++) {311              const partialClosing = "`".repeat(i);312              if (content.endsWith(partialClosing)) {313                content = content.substring(0, content.length - i);314                break;315              }316            }317            content = content.trim();318            modifiedFiles.set(path, { path, content });319          }320        }321      }322    }323  }324 325  // Convert modified files map to array326  const files = Array.from(modifiedFiles.values())?.filter(327    (file) => file.path !== "README.md"328  );329 330  return {331    messageContent,332    files,333    projectTitle,334  };335};336