Spaces:
Runtime error
Runtime error
import { createTwoFilesPatch } from 'diff'; | |
import type { FileMap } from '~/lib/stores/files'; | |
import { MODIFICATIONS_TAG_NAME, WORK_DIR } from './constants'; | |
export const modificationsRegex = new RegExp( | |
`^<${MODIFICATIONS_TAG_NAME}>[\\s\\S]*?<\\/${MODIFICATIONS_TAG_NAME}>\\s+`, | |
'g', | |
); | |
interface ModifiedFile { | |
type: 'diff' | 'file'; | |
content: string; | |
} | |
type FileModifications = Record<string, ModifiedFile>; | |
export function computeFileModifications(files: FileMap, modifiedFiles: Map<string, string>) { | |
const modifications: FileModifications = {}; | |
let hasModifiedFiles = false; | |
for (const [filePath, originalContent] of modifiedFiles) { | |
const file = files[filePath]; | |
if (file?.type !== 'file') { | |
continue; | |
} | |
const unifiedDiff = diffFiles(filePath, originalContent, file.content); | |
if (!unifiedDiff) { | |
// files are identical | |
continue; | |
} | |
hasModifiedFiles = true; | |
if (unifiedDiff.length > file.content.length) { | |
// if there are lots of changes we simply grab the current file content since it's smaller than the diff | |
modifications[filePath] = { type: 'file', content: file.content }; | |
} else { | |
// otherwise we use the diff since it's smaller | |
modifications[filePath] = { type: 'diff', content: unifiedDiff }; | |
} | |
} | |
if (!hasModifiedFiles) { | |
return undefined; | |
} | |
return modifications; | |
} | |
/** | |
* Computes a diff in the unified format. The only difference is that the header is omitted | |
* because it will always assume that you're comparing two versions of the same file and | |
* it allows us to avoid the extra characters we send back to the llm. | |
* | |
* @see https://www.gnu.org/software/diffutils/manual/html_node/Unified-Format.html | |
*/ | |
export function diffFiles(fileName: string, oldFileContent: string, newFileContent: string) { | |
let unifiedDiff = createTwoFilesPatch(fileName, fileName, oldFileContent, newFileContent); | |
const patchHeaderEnd = `--- ${fileName}\n+++ ${fileName}\n`; | |
const headerEndIndex = unifiedDiff.indexOf(patchHeaderEnd); | |
if (headerEndIndex >= 0) { | |
unifiedDiff = unifiedDiff.slice(headerEndIndex + patchHeaderEnd.length); | |
} | |
if (unifiedDiff === '') { | |
return undefined; | |
} | |
return unifiedDiff; | |
} | |
const regex = new RegExp(`^${WORK_DIR}\/`); | |
/** | |
* Strips out the work directory from the file path. | |
*/ | |
export function extractRelativePath(filePath: string) { | |
return filePath.replace(regex, ''); | |
} | |
/** | |
* Converts the unified diff to HTML. | |
* | |
* Example: | |
* | |
* ```html | |
* <bolt_file_modifications> | |
* <diff path="/home/project/index.js"> | |
* - console.log('Hello, World!'); | |
* + console.log('Hello, Bolt!'); | |
* </diff> | |
* </bolt_file_modifications> | |
* ``` | |
*/ | |
export function fileModificationsToHTML(modifications: FileModifications) { | |
const entries = Object.entries(modifications); | |
if (entries.length === 0) { | |
return undefined; | |
} | |
const result: string[] = [`<${MODIFICATIONS_TAG_NAME}>`]; | |
for (const [filePath, { type, content }] of entries) { | |
result.push(`<${type} path=${JSON.stringify(filePath)}>`, content, `</${type}>`); | |
} | |
result.push(`</${MODIFICATIONS_TAG_NAME}>`); | |
return result.join('\n'); | |
} | |