fix: fix enhance prompt to stop implementing full project instead of enhancing (#1383) #release
* fix: enhance prompt fix * fix: added error capture on api error * fix: replaced error with log for wrong files selected by bolt
This commit is contained in:
@@ -4,11 +4,6 @@ import { classNames } from '~/utils/classNames';
|
|||||||
import { cubicEasingFn } from '~/utils/easings';
|
import { cubicEasingFn } from '~/utils/easings';
|
||||||
import { genericMemo } from '~/utils/react';
|
import { genericMemo } from '~/utils/react';
|
||||||
|
|
||||||
interface SliderOption<T> {
|
|
||||||
value: T;
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SliderOptions<T> = {
|
export type SliderOptions<T> = {
|
||||||
left: { value: T; text: string };
|
left: { value: T; text: string };
|
||||||
middle?: { value: T; text: string };
|
middle?: { value: T; text: string };
|
||||||
@@ -38,7 +33,10 @@ export const Slider = genericMemo(<T,>({ selected, options, setSelected }: Slide
|
|||||||
</SliderButton>
|
</SliderButton>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<SliderButton selected={!isLeftSelected && !isMiddleSelected} setSelected={() => setSelected?.(options.right.value)}>
|
<SliderButton
|
||||||
|
selected={!isLeftSelected && !isMiddleSelected}
|
||||||
|
setSelected={() => setSelected?.(options.right.value)}
|
||||||
|
>
|
||||||
{options.right.text}
|
{options.right.text}
|
||||||
</SliderButton>
|
</SliderButton>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,14 +41,16 @@ const FullscreenButton = memo(({ onClick, isFullscreen }: FullscreenButtonProps)
|
|||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className="ml-4 p-1 rounded hover:bg-bolt-elements-background-depth-3 text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary transition-colors"
|
className="ml-4 p-1 rounded hover:bg-bolt-elements-background-depth-3 text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary transition-colors"
|
||||||
title={isFullscreen ? "Exit Fullscreen" : "Enter Fullscreen"}
|
title={isFullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen'}
|
||||||
>
|
>
|
||||||
<div className={isFullscreen ? "i-ph:corners-in" : "i-ph:corners-out"} />
|
<div className={isFullscreen ? 'i-ph:corners-in' : 'i-ph:corners-out'} />
|
||||||
</button>
|
</button>
|
||||||
));
|
));
|
||||||
|
|
||||||
const FullscreenOverlay = memo(({ isFullscreen, children }: { isFullscreen: boolean; children: React.ReactNode }) => {
|
const FullscreenOverlay = memo(({ isFullscreen, children }: { isFullscreen: boolean; children: React.ReactNode }) => {
|
||||||
if (!isFullscreen) return <>{children}</>;
|
if (!isFullscreen) {
|
||||||
|
return <>{children}</>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-[9999] bg-black/50 flex items-center justify-center p-6">
|
<div className="fixed inset-0 z-[9999] bg-black/50 flex items-center justify-center p-6">
|
||||||
@@ -75,7 +77,7 @@ const processChanges = (beforeCode: string, afterCode: string) => {
|
|||||||
hasChanges: false,
|
hasChanges: false,
|
||||||
lineChanges: { before: new Set(), after: new Set() },
|
lineChanges: { before: new Set(), after: new Set() },
|
||||||
unifiedBlocks: [],
|
unifiedBlocks: [],
|
||||||
isBinary: true
|
isBinary: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,7 +86,7 @@ const processChanges = (beforeCode: string, afterCode: string) => {
|
|||||||
return content
|
return content
|
||||||
.replace(/\r\n/g, '\n')
|
.replace(/\r\n/g, '\n')
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map(line => line.trimEnd());
|
.map((line) => line.trimEnd());
|
||||||
};
|
};
|
||||||
|
|
||||||
const beforeLines = normalizeContent(beforeCode);
|
const beforeLines = normalizeContent(beforeCode);
|
||||||
@@ -98,19 +100,21 @@ const processChanges = (beforeCode: string, afterCode: string) => {
|
|||||||
hasChanges: false,
|
hasChanges: false,
|
||||||
lineChanges: { before: new Set(), after: new Set() },
|
lineChanges: { before: new Set(), after: new Set() },
|
||||||
unifiedBlocks: [],
|
unifiedBlocks: [],
|
||||||
isBinary: false
|
isBinary: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const lineChanges = {
|
const lineChanges = {
|
||||||
before: new Set<number>(),
|
before: new Set<number>(),
|
||||||
after: new Set<number>()
|
after: new Set<number>(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const unifiedBlocks: DiffBlock[] = [];
|
const unifiedBlocks: DiffBlock[] = [];
|
||||||
|
|
||||||
// Compare lines directly for more accurate diff
|
// Compare lines directly for more accurate diff
|
||||||
let i = 0, j = 0;
|
let i = 0,
|
||||||
|
j = 0;
|
||||||
|
|
||||||
while (i < beforeLines.length || j < afterLines.length) {
|
while (i < beforeLines.length || j < afterLines.length) {
|
||||||
if (i < beforeLines.length && j < afterLines.length && beforeLines[i] === afterLines[j]) {
|
if (i < beforeLines.length && j < afterLines.length && beforeLines[i] === afterLines[j]) {
|
||||||
// Unchanged line
|
// Unchanged line
|
||||||
@@ -118,7 +122,7 @@ const processChanges = (beforeCode: string, afterCode: string) => {
|
|||||||
lineNumber: j,
|
lineNumber: j,
|
||||||
content: afterLines[j],
|
content: afterLines[j],
|
||||||
type: 'unchanged',
|
type: 'unchanged',
|
||||||
correspondingLine: i
|
correspondingLine: i,
|
||||||
});
|
});
|
||||||
i++;
|
i++;
|
||||||
j++;
|
j++;
|
||||||
@@ -138,7 +142,7 @@ const processChanges = (beforeCode: string, afterCode: string) => {
|
|||||||
content: beforeLines[i + l],
|
content: beforeLines[i + l],
|
||||||
type: 'removed',
|
type: 'removed',
|
||||||
correspondingLine: j,
|
correspondingLine: j,
|
||||||
charChanges: [{ value: beforeLines[i + l], type: 'removed' }]
|
charChanges: [{ value: beforeLines[i + l], type: 'removed' }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
i += k;
|
i += k;
|
||||||
@@ -153,7 +157,7 @@ const processChanges = (beforeCode: string, afterCode: string) => {
|
|||||||
content: afterLines[j + l],
|
content: afterLines[j + l],
|
||||||
type: 'added',
|
type: 'added',
|
||||||
correspondingLine: i,
|
correspondingLine: i,
|
||||||
charChanges: [{ value: afterLines[j + l], type: 'added' }]
|
charChanges: [{ value: afterLines[j + l], type: 'added' }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
j += k;
|
j += k;
|
||||||
@@ -170,17 +174,22 @@ const processChanges = (beforeCode: string, afterCode: string) => {
|
|||||||
|
|
||||||
// Find common prefix and suffix
|
// Find common prefix and suffix
|
||||||
let prefixLength = 0;
|
let prefixLength = 0;
|
||||||
while (prefixLength < beforeLine.length &&
|
|
||||||
|
while (
|
||||||
|
prefixLength < beforeLine.length &&
|
||||||
prefixLength < afterLine.length &&
|
prefixLength < afterLine.length &&
|
||||||
beforeLine[prefixLength] === afterLine[prefixLength]) {
|
beforeLine[prefixLength] === afterLine[prefixLength]
|
||||||
|
) {
|
||||||
prefixLength++;
|
prefixLength++;
|
||||||
}
|
}
|
||||||
|
|
||||||
let suffixLength = 0;
|
let suffixLength = 0;
|
||||||
while (suffixLength < beforeLine.length - prefixLength &&
|
|
||||||
|
while (
|
||||||
|
suffixLength < beforeLine.length - prefixLength &&
|
||||||
suffixLength < afterLine.length - prefixLength &&
|
suffixLength < afterLine.length - prefixLength &&
|
||||||
beforeLine[beforeLine.length - 1 - suffixLength] ===
|
beforeLine[beforeLine.length - 1 - suffixLength] === afterLine[afterLine.length - 1 - suffixLength]
|
||||||
afterLine[afterLine.length - 1 - suffixLength]) {
|
) {
|
||||||
suffixLength++;
|
suffixLength++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,11 +210,12 @@ const processChanges = (beforeCode: string, afterCode: string) => {
|
|||||||
charChanges: [
|
charChanges: [
|
||||||
{ value: prefix, type: 'unchanged' },
|
{ value: prefix, type: 'unchanged' },
|
||||||
{ value: beforeMiddle, type: 'removed' },
|
{ value: beforeMiddle, type: 'removed' },
|
||||||
{ value: suffix, type: 'unchanged' }
|
{ value: suffix, type: 'unchanged' },
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (afterMiddle) {
|
if (afterMiddle) {
|
||||||
lineChanges.after.add(j);
|
lineChanges.after.add(j);
|
||||||
unifiedBlocks.push({
|
unifiedBlocks.push({
|
||||||
@@ -216,8 +226,8 @@ const processChanges = (beforeCode: string, afterCode: string) => {
|
|||||||
charChanges: [
|
charChanges: [
|
||||||
{ value: prefix, type: 'unchanged' },
|
{ value: prefix, type: 'unchanged' },
|
||||||
{ value: afterMiddle, type: 'added' },
|
{ value: afterMiddle, type: 'added' },
|
||||||
{ value: suffix, type: 'unchanged' }
|
{ value: suffix, type: 'unchanged' },
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
j++;
|
j++;
|
||||||
}
|
}
|
||||||
@@ -230,10 +240,11 @@ const processChanges = (beforeCode: string, afterCode: string) => {
|
|||||||
content: beforeLines[i],
|
content: beforeLines[i],
|
||||||
type: 'removed',
|
type: 'removed',
|
||||||
correspondingLine: j,
|
correspondingLine: j,
|
||||||
charChanges: [{ value: beforeLines[i], type: 'removed' }]
|
charChanges: [{ value: beforeLines[i], type: 'removed' }],
|
||||||
});
|
});
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (j < afterLines.length) {
|
if (j < afterLines.length) {
|
||||||
lineChanges.after.add(j);
|
lineChanges.after.add(j);
|
||||||
unifiedBlocks.push({
|
unifiedBlocks.push({
|
||||||
@@ -241,7 +252,7 @@ const processChanges = (beforeCode: string, afterCode: string) => {
|
|||||||
content: afterLines[j],
|
content: afterLines[j],
|
||||||
type: 'added',
|
type: 'added',
|
||||||
correspondingLine: i - 1,
|
correspondingLine: i - 1,
|
||||||
charChanges: [{ value: afterLines[j], type: 'added' }]
|
charChanges: [{ value: afterLines[j], type: 'added' }],
|
||||||
});
|
});
|
||||||
j++;
|
j++;
|
||||||
}
|
}
|
||||||
@@ -255,10 +266,11 @@ const processChanges = (beforeCode: string, afterCode: string) => {
|
|||||||
content: beforeLines[i],
|
content: beforeLines[i],
|
||||||
type: 'removed',
|
type: 'removed',
|
||||||
correspondingLine: j,
|
correspondingLine: j,
|
||||||
charChanges: [{ value: beforeLines[i], type: 'removed' }]
|
charChanges: [{ value: beforeLines[i], type: 'removed' }],
|
||||||
});
|
});
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (j < afterLines.length) {
|
if (j < afterLines.length) {
|
||||||
lineChanges.after.add(j);
|
lineChanges.after.add(j);
|
||||||
unifiedBlocks.push({
|
unifiedBlocks.push({
|
||||||
@@ -266,7 +278,7 @@ const processChanges = (beforeCode: string, afterCode: string) => {
|
|||||||
content: afterLines[j],
|
content: afterLines[j],
|
||||||
type: 'added',
|
type: 'added',
|
||||||
correspondingLine: i - 1,
|
correspondingLine: i - 1,
|
||||||
charChanges: [{ value: afterLines[j], type: 'added' }]
|
charChanges: [{ value: afterLines[j], type: 'added' }],
|
||||||
});
|
});
|
||||||
j++;
|
j++;
|
||||||
}
|
}
|
||||||
@@ -284,7 +296,7 @@ const processChanges = (beforeCode: string, afterCode: string) => {
|
|||||||
hasChanges: lineChanges.before.size > 0 || lineChanges.after.size > 0,
|
hasChanges: lineChanges.before.size > 0 || lineChanges.after.size > 0,
|
||||||
lineChanges,
|
lineChanges,
|
||||||
unifiedBlocks: processedBlocks,
|
unifiedBlocks: processedBlocks,
|
||||||
isBinary: false
|
isBinary: false,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing changes:', error);
|
console.error('Error processing changes:', error);
|
||||||
@@ -295,26 +307,28 @@ const processChanges = (beforeCode: string, afterCode: string) => {
|
|||||||
lineChanges: { before: new Set(), after: new Set() },
|
lineChanges: { before: new Set(), after: new Set() },
|
||||||
unifiedBlocks: [],
|
unifiedBlocks: [],
|
||||||
error: true,
|
error: true,
|
||||||
isBinary: false
|
isBinary: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const lineNumberStyles = "w-9 shrink-0 pl-2 py-1 text-left font-mono text-bolt-elements-textTertiary border-r border-bolt-elements-borderColor bg-bolt-elements-background-depth-1";
|
const lineNumberStyles =
|
||||||
const lineContentStyles = "px-1 py-1 font-mono whitespace-pre flex-1 group-hover:bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary";
|
'w-9 shrink-0 pl-2 py-1 text-left font-mono text-bolt-elements-textTertiary border-r border-bolt-elements-borderColor bg-bolt-elements-background-depth-1';
|
||||||
const diffPanelStyles = "h-full overflow-auto diff-panel-content";
|
const lineContentStyles =
|
||||||
|
'px-1 py-1 font-mono whitespace-pre flex-1 group-hover:bg-bolt-elements-background-depth-2 text-bolt-elements-textPrimary';
|
||||||
|
const diffPanelStyles = 'h-full overflow-auto diff-panel-content';
|
||||||
|
|
||||||
// Updated color styles for better consistency
|
// Updated color styles for better consistency
|
||||||
const diffLineStyles = {
|
const diffLineStyles = {
|
||||||
added: 'bg-green-500/10 dark:bg-green-500/20 border-l-4 border-green-500',
|
added: 'bg-green-500/10 dark:bg-green-500/20 border-l-4 border-green-500',
|
||||||
removed: 'bg-red-500/10 dark:bg-red-500/20 border-l-4 border-red-500',
|
removed: 'bg-red-500/10 dark:bg-red-500/20 border-l-4 border-red-500',
|
||||||
unchanged: ''
|
unchanged: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeColorStyles = {
|
const changeColorStyles = {
|
||||||
added: 'text-green-700 dark:text-green-500 bg-green-500/10 dark:bg-green-500/20',
|
added: 'text-green-700 dark:text-green-500 bg-green-500/10 dark:bg-green-500/20',
|
||||||
removed: 'text-red-700 dark:text-red-500 bg-red-500/10 dark:bg-red-500/20',
|
removed: 'text-red-700 dark:text-red-500 bg-red-500/10 dark:bg-red-500/20',
|
||||||
unchanged: 'text-bolt-elements-textPrimary'
|
unchanged: 'text-bolt-elements-textPrimary',
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderContentWarning = (type: 'binary' | 'error') => (
|
const renderContentWarning = (type: 'binary' | 'error') => (
|
||||||
@@ -325,20 +339,24 @@ const renderContentWarning = (type: 'binary' | 'error') => (
|
|||||||
{type === 'binary' ? 'Binary file detected' : 'Error processing file'}
|
{type === 'binary' ? 'Binary file detected' : 'Error processing file'}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-sm mt-1">
|
<p className="text-sm mt-1">
|
||||||
{type === 'binary'
|
{type === 'binary' ? 'Diff view is not available for binary files' : 'Could not generate diff preview'}
|
||||||
? 'Diff view is not available for binary files'
|
|
||||||
: 'Could not generate diff preview'}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const NoChangesView = memo(({ beforeCode, language, highlighter, theme }: {
|
const NoChangesView = memo(
|
||||||
|
({
|
||||||
|
beforeCode,
|
||||||
|
language,
|
||||||
|
highlighter,
|
||||||
|
theme,
|
||||||
|
}: {
|
||||||
beforeCode: string;
|
beforeCode: string;
|
||||||
language: string;
|
language: string;
|
||||||
highlighter: any;
|
highlighter: any;
|
||||||
theme: string;
|
theme: string;
|
||||||
}) => (
|
}) => (
|
||||||
<div className="h-full flex flex-col items-center justify-center p-4">
|
<div className="h-full flex flex-col items-center justify-center p-4">
|
||||||
<div className="text-center text-bolt-elements-textTertiary">
|
<div className="text-center text-bolt-elements-textTertiary">
|
||||||
<div className="i-ph:files text-4xl text-green-400 mb-2 mx-auto" />
|
<div className="i-ph:files text-4xl text-green-400 mb-2 mx-auto" />
|
||||||
@@ -355,20 +373,27 @@ const NoChangesView = memo(({ beforeCode, language, highlighter, theme }: {
|
|||||||
<div className={lineNumberStyles}>{index + 1}</div>
|
<div className={lineNumberStyles}>{index + 1}</div>
|
||||||
<div className={lineContentStyles}>
|
<div className={lineContentStyles}>
|
||||||
<span className="mr-2"> </span>
|
<span className="mr-2"> </span>
|
||||||
<span dangerouslySetInnerHTML={{
|
<span
|
||||||
__html: highlighter ?
|
dangerouslySetInnerHTML={{
|
||||||
highlighter.codeToHtml(line, { lang: language, theme: theme === 'dark' ? 'github-dark' : 'github-light' })
|
__html: highlighter
|
||||||
|
? highlighter
|
||||||
|
.codeToHtml(line, {
|
||||||
|
lang: language,
|
||||||
|
theme: theme === 'dark' ? 'github-dark' : 'github-light',
|
||||||
|
})
|
||||||
.replace(/<\/?pre[^>]*>/g, '')
|
.replace(/<\/?pre[^>]*>/g, '')
|
||||||
.replace(/<\/?code[^>]*>/g, '')
|
.replace(/<\/?code[^>]*>/g, '')
|
||||||
: line
|
: line,
|
||||||
}} />
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
));
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// Otimização do processamento de diferenças com memoização
|
// Otimização do processamento de diferenças com memoização
|
||||||
const useProcessChanges = (beforeCode: string, afterCode: string) => {
|
const useProcessChanges = (beforeCode: string, afterCode: string) => {
|
||||||
@@ -376,15 +401,16 @@ const useProcessChanges = (beforeCode: string, afterCode: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Componente otimizado para renderização de linhas de código
|
// Componente otimizado para renderização de linhas de código
|
||||||
const CodeLine = memo(({
|
const CodeLine = memo(
|
||||||
|
({
|
||||||
lineNumber,
|
lineNumber,
|
||||||
content,
|
content,
|
||||||
type,
|
type,
|
||||||
highlighter,
|
highlighter,
|
||||||
language,
|
language,
|
||||||
block,
|
block,
|
||||||
theme
|
theme,
|
||||||
}: {
|
}: {
|
||||||
lineNumber: number;
|
lineNumber: number;
|
||||||
content: string;
|
content: string;
|
||||||
type: 'added' | 'removed' | 'unchanged';
|
type: 'added' | 'removed' | 'unchanged';
|
||||||
@@ -392,13 +418,14 @@ const CodeLine = memo(({
|
|||||||
language: string;
|
language: string;
|
||||||
block: DiffBlock;
|
block: DiffBlock;
|
||||||
theme: string;
|
theme: string;
|
||||||
}) => {
|
}) => {
|
||||||
const bgColor = diffLineStyles[type];
|
const bgColor = diffLineStyles[type];
|
||||||
|
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
if (type === 'unchanged' || !block.charChanges) {
|
if (type === 'unchanged' || !block.charChanges) {
|
||||||
const highlightedCode = highlighter ?
|
const highlightedCode = highlighter
|
||||||
highlighter.codeToHtml(content, { lang: language, theme: theme === 'dark' ? 'github-dark' : 'github-light' })
|
? highlighter
|
||||||
|
.codeToHtml(content, { lang: language, theme: theme === 'dark' ? 'github-dark' : 'github-light' })
|
||||||
.replace(/<\/?pre[^>]*>/g, '')
|
.replace(/<\/?pre[^>]*>/g, '')
|
||||||
.replace(/<\/?code[^>]*>/g, '')
|
.replace(/<\/?code[^>]*>/g, '')
|
||||||
: content;
|
: content;
|
||||||
@@ -410,19 +437,17 @@ const CodeLine = memo(({
|
|||||||
{block.charChanges.map((change, index) => {
|
{block.charChanges.map((change, index) => {
|
||||||
const changeClass = changeColorStyles[change.type];
|
const changeClass = changeColorStyles[change.type];
|
||||||
|
|
||||||
const highlightedCode = highlighter ?
|
const highlightedCode = highlighter
|
||||||
highlighter.codeToHtml(change.value, { lang: language, theme: theme === 'dark' ? 'github-dark' : 'github-light' })
|
? highlighter
|
||||||
|
.codeToHtml(change.value, {
|
||||||
|
lang: language,
|
||||||
|
theme: theme === 'dark' ? 'github-dark' : 'github-light',
|
||||||
|
})
|
||||||
.replace(/<\/?pre[^>]*>/g, '')
|
.replace(/<\/?pre[^>]*>/g, '')
|
||||||
.replace(/<\/?code[^>]*>/g, '')
|
.replace(/<\/?code[^>]*>/g, '')
|
||||||
: change.value;
|
: change.value;
|
||||||
|
|
||||||
return (
|
return <span key={index} className={changeClass} dangerouslySetInnerHTML={{ __html: highlightedCode }} />;
|
||||||
<span
|
|
||||||
key={index}
|
|
||||||
className={changeClass}
|
|
||||||
dangerouslySetInnerHTML={{ __html: highlightedCode }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -441,43 +466,52 @@ const CodeLine = memo(({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// Componente para exibir informações sobre o arquivo
|
// Componente para exibir informações sobre o arquivo
|
||||||
const FileInfo = memo(({
|
const FileInfo = memo(
|
||||||
|
({
|
||||||
filename,
|
filename,
|
||||||
hasChanges,
|
hasChanges,
|
||||||
onToggleFullscreen,
|
onToggleFullscreen,
|
||||||
isFullscreen,
|
isFullscreen,
|
||||||
beforeCode,
|
beforeCode,
|
||||||
afterCode
|
afterCode,
|
||||||
}: {
|
}: {
|
||||||
filename: string;
|
filename: string;
|
||||||
hasChanges: boolean;
|
hasChanges: boolean;
|
||||||
onToggleFullscreen: () => void;
|
onToggleFullscreen: () => void;
|
||||||
isFullscreen: boolean;
|
isFullscreen: boolean;
|
||||||
beforeCode: string;
|
beforeCode: string;
|
||||||
afterCode: string;
|
afterCode: string;
|
||||||
}) => {
|
}) => {
|
||||||
// Calculate additions and deletions from the current document
|
// Calculate additions and deletions from the current document
|
||||||
const { additions, deletions } = useMemo(() => {
|
const { additions, deletions } = useMemo(() => {
|
||||||
if (!hasChanges) return { additions: 0, deletions: 0 };
|
if (!hasChanges) {
|
||||||
|
return { additions: 0, deletions: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
const changes = diffLines(beforeCode, afterCode, {
|
const changes = diffLines(beforeCode, afterCode, {
|
||||||
newlineIsToken: false,
|
newlineIsToken: false,
|
||||||
ignoreWhitespace: true,
|
ignoreWhitespace: true,
|
||||||
ignoreCase: false
|
ignoreCase: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return changes.reduce((acc: { additions: number; deletions: number }, change: Change) => {
|
return changes.reduce(
|
||||||
|
(acc: { additions: number; deletions: number }, change: Change) => {
|
||||||
if (change.added) {
|
if (change.added) {
|
||||||
acc.additions += change.value.split('\n').length;
|
acc.additions += change.value.split('\n').length;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (change.removed) {
|
if (change.removed) {
|
||||||
acc.deletions += change.value.split('\n').length;
|
acc.deletions += change.value.split('\n').length;
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, { additions: 0, deletions: 0 });
|
},
|
||||||
|
{ additions: 0, deletions: 0 },
|
||||||
|
);
|
||||||
}, [hasChanges, beforeCode, afterCode]);
|
}, [hasChanges, beforeCode, afterCode]);
|
||||||
|
|
||||||
const showStats = additions > 0 || deletions > 0;
|
const showStats = additions > 0 || deletions > 0;
|
||||||
@@ -491,18 +525,12 @@ const FileInfo = memo(({
|
|||||||
<>
|
<>
|
||||||
{showStats && (
|
{showStats && (
|
||||||
<div className="flex items-center gap-1 text-xs">
|
<div className="flex items-center gap-1 text-xs">
|
||||||
{additions > 0 && (
|
{additions > 0 && <span className="text-green-700 dark:text-green-500">+{additions}</span>}
|
||||||
<span className="text-green-700 dark:text-green-500">+{additions}</span>
|
{deletions > 0 && <span className="text-red-700 dark:text-red-500">-{deletions}</span>}
|
||||||
)}
|
|
||||||
{deletions > 0 && (
|
|
||||||
<span className="text-red-700 dark:text-red-500">-{deletions}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<span className="text-yellow-600 dark:text-yellow-400">Modified</span>
|
<span className="text-yellow-600 dark:text-yellow-400">Modified</span>
|
||||||
<span className="text-bolt-elements-textTertiary text-xs">
|
<span className="text-bolt-elements-textTertiary text-xs">{new Date().toLocaleTimeString()}</span>
|
||||||
{new Date().toLocaleTimeString()}
|
|
||||||
</span>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-green-700 dark:text-green-400">No Changes</span>
|
<span className="text-green-700 dark:text-green-400">No Changes</span>
|
||||||
@@ -511,15 +539,16 @@ const FileInfo = memo(({
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language, lightTheme, darkTheme }: CodeComparisonProps) => {
|
const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language }: CodeComparisonProps) => {
|
||||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||||
const [highlighter, setHighlighter] = useState<any>(null);
|
const [highlighter, setHighlighter] = useState<any>(null);
|
||||||
const theme = useStore(themeStore);
|
const theme = useStore(themeStore);
|
||||||
|
|
||||||
const toggleFullscreen = useCallback(() => {
|
const toggleFullscreen = useCallback(() => {
|
||||||
setIsFullscreen(prev => !prev);
|
setIsFullscreen((prev) => !prev);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const { unifiedBlocks, hasChanges, isBinary, error } = useProcessChanges(beforeCode, afterCode);
|
const { unifiedBlocks, hasChanges, isBinary, error } = useProcessChanges(beforeCode, afterCode);
|
||||||
@@ -527,11 +556,13 @@ const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language,
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getHighlighter({
|
getHighlighter({
|
||||||
themes: ['github-dark', 'github-light'],
|
themes: ['github-dark', 'github-light'],
|
||||||
langs: ['typescript', 'javascript', 'json', 'html', 'css', 'jsx', 'tsx']
|
langs: ['typescript', 'javascript', 'json', 'html', 'css', 'jsx', 'tsx'],
|
||||||
}).then(setHighlighter);
|
}).then(setHighlighter);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (isBinary || error) return renderContentWarning(isBinary ? 'binary' : 'error');
|
if (isBinary || error) {
|
||||||
|
return renderContentWarning(isBinary ? 'binary' : 'error');
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FullscreenOverlay isFullscreen={isFullscreen}>
|
<FullscreenOverlay isFullscreen={isFullscreen}>
|
||||||
@@ -561,12 +592,7 @@ const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language,
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<NoChangesView
|
<NoChangesView beforeCode={beforeCode} language={language} highlighter={highlighter} theme={theme} />
|
||||||
beforeCode={beforeCode}
|
|
||||||
language={language}
|
|
||||||
highlighter={highlighter}
|
|
||||||
theme={theme}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -580,7 +606,7 @@ interface DiffViewProps {
|
|||||||
actionRunner: ActionRunner;
|
actionRunner: ActionRunner;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DiffView = memo(({ fileHistory, setFileHistory, actionRunner }: DiffViewProps) => {
|
export const DiffView = memo(({ fileHistory, setFileHistory }: DiffViewProps) => {
|
||||||
const files = useStore(workbenchStore.files) as FileMap;
|
const files = useStore(workbenchStore.files) as FileMap;
|
||||||
const selectedFile = useStore(workbenchStore.selectedFile);
|
const selectedFile = useStore(workbenchStore.selectedFile);
|
||||||
const currentDocument = useStore(workbenchStore.currentDocument) as EditorDocument;
|
const currentDocument = useStore(workbenchStore.currentDocument) as EditorDocument;
|
||||||
@@ -589,33 +615,41 @@ export const DiffView = memo(({ fileHistory, setFileHistory, actionRunner }: Dif
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedFile && currentDocument) {
|
if (selectedFile && currentDocument) {
|
||||||
const file = files[selectedFile];
|
const file = files[selectedFile];
|
||||||
if (!file || !('content' in file)) return;
|
|
||||||
|
if (!file || !('content' in file)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const existingHistory = fileHistory[selectedFile];
|
const existingHistory = fileHistory[selectedFile];
|
||||||
const currentContent = currentDocument.value;
|
const currentContent = currentDocument.value;
|
||||||
|
|
||||||
// Normalizar o conteúdo para comparação
|
// Normalizar o conteúdo para comparação
|
||||||
const normalizedCurrentContent = currentContent.replace(/\r\n/g, '\n').trim();
|
const normalizedCurrentContent = currentContent.replace(/\r\n/g, '\n').trim();
|
||||||
const normalizedOriginalContent = (existingHistory?.originalContent || file.content).replace(/\r\n/g, '\n').trim();
|
const normalizedOriginalContent = (existingHistory?.originalContent || file.content)
|
||||||
|
.replace(/\r\n/g, '\n')
|
||||||
|
.trim();
|
||||||
|
|
||||||
// Se não há histórico existente, criar um novo apenas se houver diferenças
|
// Se não há histórico existente, criar um novo apenas se houver diferenças
|
||||||
if (!existingHistory) {
|
if (!existingHistory) {
|
||||||
if (normalizedCurrentContent !== normalizedOriginalContent) {
|
if (normalizedCurrentContent !== normalizedOriginalContent) {
|
||||||
const newChanges = diffLines(file.content, currentContent);
|
const newChanges = diffLines(file.content, currentContent);
|
||||||
setFileHistory(prev => ({
|
setFileHistory((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[selectedFile]: {
|
[selectedFile]: {
|
||||||
originalContent: file.content,
|
originalContent: file.content,
|
||||||
lastModified: Date.now(),
|
lastModified: Date.now(),
|
||||||
changes: newChanges,
|
changes: newChanges,
|
||||||
versions: [{
|
versions: [
|
||||||
|
{
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
content: currentContent
|
content: currentContent,
|
||||||
}],
|
},
|
||||||
changeSource: 'auto-save'
|
],
|
||||||
}
|
changeSource: 'auto-save',
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -629,42 +663,32 @@ export const DiffView = memo(({ fileHistory, setFileHistory, actionRunner }: Dif
|
|||||||
|
|
||||||
// Verificar se há mudanças significativas usando diffFiles
|
// Verificar se há mudanças significativas usando diffFiles
|
||||||
const relativePath = extractRelativePath(selectedFile);
|
const relativePath = extractRelativePath(selectedFile);
|
||||||
const unifiedDiff = diffFiles(
|
const unifiedDiff = diffFiles(relativePath, existingHistory.originalContent, currentContent);
|
||||||
relativePath,
|
|
||||||
existingHistory.originalContent,
|
|
||||||
currentContent
|
|
||||||
);
|
|
||||||
|
|
||||||
if (unifiedDiff) {
|
if (unifiedDiff) {
|
||||||
const newChanges = diffLines(
|
const newChanges = diffLines(existingHistory.originalContent, currentContent);
|
||||||
existingHistory.originalContent,
|
|
||||||
currentContent
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verificar se as mudanças são significativas
|
// Verificar se as mudanças são significativas
|
||||||
const hasSignificantChanges = newChanges.some(change =>
|
const hasSignificantChanges = newChanges.some(
|
||||||
(change.added || change.removed) && change.value.trim().length > 0
|
(change) => (change.added || change.removed) && change.value.trim().length > 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hasSignificantChanges) {
|
if (hasSignificantChanges) {
|
||||||
const newHistory: FileHistory = {
|
const newHistory: FileHistory = {
|
||||||
originalContent: existingHistory.originalContent,
|
originalContent: existingHistory.originalContent,
|
||||||
lastModified: Date.now(),
|
lastModified: Date.now(),
|
||||||
changes: [
|
changes: [...existingHistory.changes, ...newChanges].slice(-100), // Limitar histórico de mudanças
|
||||||
...existingHistory.changes,
|
|
||||||
...newChanges
|
|
||||||
].slice(-100), // Limitar histórico de mudanças
|
|
||||||
versions: [
|
versions: [
|
||||||
...existingHistory.versions,
|
...existingHistory.versions,
|
||||||
{
|
{
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
content: currentContent
|
content: currentContent,
|
||||||
}
|
},
|
||||||
].slice(-10), // Manter apenas as 10 últimas versões
|
].slice(-10), // Manter apenas as 10 últimas versões
|
||||||
changeSource: 'auto-save'
|
changeSource: 'auto-save',
|
||||||
};
|
};
|
||||||
|
|
||||||
setFileHistory(prev => ({ ...prev, [selectedFile]: newHistory }));
|
setFileHistory((prev) => ({ ...prev, [selectedFile]: newHistory }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,15 +274,19 @@ function File({
|
|||||||
fileHistory = {},
|
fileHistory = {},
|
||||||
}: FileProps) {
|
}: FileProps) {
|
||||||
const fileModifications = fileHistory[fullPath];
|
const fileModifications = fileHistory[fullPath];
|
||||||
const hasModifications = fileModifications !== undefined;
|
|
||||||
|
// const hasModifications = fileModifications !== undefined;
|
||||||
|
|
||||||
// Calculate added and removed lines from the most recent changes
|
// Calculate added and removed lines from the most recent changes
|
||||||
const { additions, deletions } = useMemo(() => {
|
const { additions, deletions } = useMemo(() => {
|
||||||
if (!fileModifications?.originalContent) return { additions: 0, deletions: 0 };
|
if (!fileModifications?.originalContent) {
|
||||||
|
return { additions: 0, deletions: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
// Usar a mesma lógica do DiffView para processar as mudanças
|
// Usar a mesma lógica do DiffView para processar as mudanças
|
||||||
const normalizedOriginal = fileModifications.originalContent.replace(/\r\n/g, '\n');
|
const normalizedOriginal = fileModifications.originalContent.replace(/\r\n/g, '\n');
|
||||||
const normalizedCurrent = fileModifications.versions[fileModifications.versions.length - 1]?.content.replace(/\r\n/g, '\n') || '';
|
const normalizedCurrent =
|
||||||
|
fileModifications.versions[fileModifications.versions.length - 1]?.content.replace(/\r\n/g, '\n') || '';
|
||||||
|
|
||||||
if (normalizedOriginal === normalizedCurrent) {
|
if (normalizedOriginal === normalizedCurrent) {
|
||||||
return { additions: 0, deletions: 0 };
|
return { additions: 0, deletions: 0 };
|
||||||
@@ -291,18 +295,23 @@ function File({
|
|||||||
const changes = diffLines(normalizedOriginal, normalizedCurrent, {
|
const changes = diffLines(normalizedOriginal, normalizedCurrent, {
|
||||||
newlineIsToken: false,
|
newlineIsToken: false,
|
||||||
ignoreWhitespace: true,
|
ignoreWhitespace: true,
|
||||||
ignoreCase: false
|
ignoreCase: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return changes.reduce((acc: { additions: number; deletions: number }, change: Change) => {
|
return changes.reduce(
|
||||||
|
(acc: { additions: number; deletions: number }, change: Change) => {
|
||||||
if (change.added) {
|
if (change.added) {
|
||||||
acc.additions += change.value.split('\n').length;
|
acc.additions += change.value.split('\n').length;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (change.removed) {
|
if (change.removed) {
|
||||||
acc.deletions += change.value.split('\n').length;
|
acc.deletions += change.value.split('\n').length;
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, { additions: 0, deletions: 0 });
|
},
|
||||||
|
{ additions: 0, deletions: 0 },
|
||||||
|
);
|
||||||
}, [fileModifications]);
|
}, [fileModifications]);
|
||||||
|
|
||||||
const showStats = additions > 0 || deletions > 0;
|
const showStats = additions > 0 || deletions > 0;
|
||||||
@@ -330,17 +339,11 @@ function File({
|
|||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{showStats && (
|
{showStats && (
|
||||||
<div className="flex items-center gap-1 text-xs">
|
<div className="flex items-center gap-1 text-xs">
|
||||||
{additions > 0 && (
|
{additions > 0 && <span className="text-green-500">+{additions}</span>}
|
||||||
<span className="text-green-500">+{additions}</span>
|
{deletions > 0 && <span className="text-red-500">-{deletions}</span>}
|
||||||
)}
|
|
||||||
{deletions > 0 && (
|
|
||||||
<span className="text-red-500">-{deletions}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{unsavedChanges && (
|
{unsavedChanges && <span className="i-ph:circle-fill scale-68 shrink-0 text-orange-500" />}
|
||||||
<span className="i-ph:circle-fill scale-68 shrink-0 text-orange-500" />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NodeButton>
|
</NodeButton>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { memo, useCallback, useEffect, useState, useMemo } from 'react';
|
|||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { Popover, Transition } from '@headlessui/react';
|
import { Popover, Transition } from '@headlessui/react';
|
||||||
import { diffLines, type Change } from 'diff';
|
import { diffLines, type Change } from 'diff';
|
||||||
import { formatDistanceToNow as formatDistance } from 'date-fns';
|
|
||||||
import { ActionRunner } from '~/lib/runtime/action-runner';
|
import { ActionRunner } from '~/lib/runtime/action-runner';
|
||||||
import { getLanguageFromExtension } from '~/utils/getLanguageFromExtension';
|
import { getLanguageFromExtension } from '~/utils/getLanguageFromExtension';
|
||||||
import type { FileHistory } from '~/types/actions';
|
import type { FileHistory } from '~/types/actions';
|
||||||
@@ -25,7 +24,6 @@ import { EditorPanel } from './EditorPanel';
|
|||||||
import { Preview } from './Preview';
|
import { Preview } from './Preview';
|
||||||
import useViewport from '~/lib/hooks';
|
import useViewport from '~/lib/hooks';
|
||||||
import { PushToGitHubDialog } from '~/components/@settings/tabs/connections/components/PushToGitHubDialog';
|
import { PushToGitHubDialog } from '~/components/@settings/tabs/connections/components/PushToGitHubDialog';
|
||||||
import Cookies from 'js-cookie';
|
|
||||||
|
|
||||||
interface WorkspaceProps {
|
interface WorkspaceProps {
|
||||||
chatStarted?: boolean;
|
chatStarted?: boolean;
|
||||||
@@ -71,21 +69,20 @@ const workbenchVariants = {
|
|||||||
},
|
},
|
||||||
} satisfies Variants;
|
} satisfies Variants;
|
||||||
|
|
||||||
const FileModifiedDropdown = memo(({
|
const FileModifiedDropdown = memo(
|
||||||
|
({
|
||||||
fileHistory,
|
fileHistory,
|
||||||
onSelectFile,
|
onSelectFile,
|
||||||
}: {
|
}: {
|
||||||
fileHistory: Record<string, FileHistory>,
|
fileHistory: Record<string, FileHistory>;
|
||||||
onSelectFile: (filePath: string) => void,
|
onSelectFile: (filePath: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const modifiedFiles = Object.entries(fileHistory);
|
const modifiedFiles = Object.entries(fileHistory);
|
||||||
const hasChanges = modifiedFiles.length > 0;
|
const hasChanges = modifiedFiles.length > 0;
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
|
||||||
const filteredFiles = useMemo(() => {
|
const filteredFiles = useMemo(() => {
|
||||||
return modifiedFiles.filter(([filePath]) =>
|
return modifiedFiles.filter(([filePath]) => filePath.toLowerCase().includes(searchQuery.toLowerCase()));
|
||||||
filePath.toLowerCase().includes(searchQuery.toLowerCase())
|
|
||||||
);
|
|
||||||
}, [modifiedFiles, searchQuery]);
|
}, [modifiedFiles, searchQuery]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -139,7 +136,9 @@ const FileModifiedDropdown = memo(({
|
|||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="shrink-0 w-5 h-5 text-bolt-elements-textTertiary">
|
<div className="shrink-0 w-5 h-5 text-bolt-elements-textTertiary">
|
||||||
{['typescript', 'javascript', 'jsx', 'tsx'].includes(language) && <div className="i-ph:file-js" />}
|
{['typescript', 'javascript', 'jsx', 'tsx'].includes(language) && (
|
||||||
|
<div className="i-ph:file-js" />
|
||||||
|
)}
|
||||||
{['css', 'scss', 'less'].includes(language) && <div className="i-ph:paint-brush" />}
|
{['css', 'scss', 'less'].includes(language) && <div className="i-ph:paint-brush" />}
|
||||||
{language === 'html' && <div className="i-ph:code" />}
|
{language === 'html' && <div className="i-ph:code" />}
|
||||||
{language === 'json' && <div className="i-ph:brackets-curly" />}
|
{language === 'json' && <div className="i-ph:brackets-curly" />}
|
||||||
@@ -149,7 +148,24 @@ const FileModifiedDropdown = memo(({
|
|||||||
{language === 'sql' && <div className="i-ph:database" />}
|
{language === 'sql' && <div className="i-ph:database" />}
|
||||||
{language === 'dockerfile' && <div className="i-ph:cube" />}
|
{language === 'dockerfile' && <div className="i-ph:cube" />}
|
||||||
{language === 'shell' && <div className="i-ph:terminal" />}
|
{language === 'shell' && <div className="i-ph:terminal" />}
|
||||||
{!['typescript', 'javascript', 'css', 'html', 'json', 'python', 'markdown', 'yaml', 'yml', 'sql', 'dockerfile', 'shell', 'jsx', 'tsx', 'scss', 'less'].includes(language) && <div className="i-ph:file-text" />}
|
{![
|
||||||
|
'typescript',
|
||||||
|
'javascript',
|
||||||
|
'css',
|
||||||
|
'html',
|
||||||
|
'json',
|
||||||
|
'python',
|
||||||
|
'markdown',
|
||||||
|
'yaml',
|
||||||
|
'yml',
|
||||||
|
'sql',
|
||||||
|
'dockerfile',
|
||||||
|
'shell',
|
||||||
|
'jsx',
|
||||||
|
'tsx',
|
||||||
|
'scss',
|
||||||
|
'less',
|
||||||
|
].includes(language) && <div className="i-ph:file-text" />}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
@@ -164,10 +180,16 @@ const FileModifiedDropdown = memo(({
|
|||||||
{(() => {
|
{(() => {
|
||||||
// Calculate diff stats
|
// Calculate diff stats
|
||||||
const { additions, deletions } = (() => {
|
const { additions, deletions } = (() => {
|
||||||
if (!history.originalContent) return { additions: 0, deletions: 0 };
|
if (!history.originalContent) {
|
||||||
|
return { additions: 0, deletions: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
const normalizedOriginal = history.originalContent.replace(/\r\n/g, '\n');
|
const normalizedOriginal = history.originalContent.replace(/\r\n/g, '\n');
|
||||||
const normalizedCurrent = history.versions[history.versions.length - 1]?.content.replace(/\r\n/g, '\n') || '';
|
const normalizedCurrent =
|
||||||
|
history.versions[history.versions.length - 1]?.content.replace(
|
||||||
|
/\r\n/g,
|
||||||
|
'\n',
|
||||||
|
) || '';
|
||||||
|
|
||||||
if (normalizedOriginal === normalizedCurrent) {
|
if (normalizedOriginal === normalizedCurrent) {
|
||||||
return { additions: 0, deletions: 0 };
|
return { additions: 0, deletions: 0 };
|
||||||
@@ -176,31 +198,34 @@ const FileModifiedDropdown = memo(({
|
|||||||
const changes = diffLines(normalizedOriginal, normalizedCurrent, {
|
const changes = diffLines(normalizedOriginal, normalizedCurrent, {
|
||||||
newlineIsToken: false,
|
newlineIsToken: false,
|
||||||
ignoreWhitespace: true,
|
ignoreWhitespace: true,
|
||||||
ignoreCase: false
|
ignoreCase: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return changes.reduce((acc: { additions: number; deletions: number }, change: Change) => {
|
return changes.reduce(
|
||||||
|
(acc: { additions: number; deletions: number }, change: Change) => {
|
||||||
if (change.added) {
|
if (change.added) {
|
||||||
acc.additions += change.value.split('\n').length;
|
acc.additions += change.value.split('\n').length;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (change.removed) {
|
if (change.removed) {
|
||||||
acc.deletions += change.value.split('\n').length;
|
acc.deletions += change.value.split('\n').length;
|
||||||
}
|
}
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, { additions: 0, deletions: 0 });
|
},
|
||||||
|
{ additions: 0, deletions: 0 },
|
||||||
|
);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const showStats = additions > 0 || deletions > 0;
|
const showStats = additions > 0 || deletions > 0;
|
||||||
|
|
||||||
return showStats && (
|
return (
|
||||||
|
showStats && (
|
||||||
<div className="flex items-center gap-1 text-xs shrink-0">
|
<div className="flex items-center gap-1 text-xs shrink-0">
|
||||||
{additions > 0 && (
|
{additions > 0 && <span className="text-green-500">+{additions}</span>}
|
||||||
<span className="text-green-500">+{additions}</span>
|
{deletions > 0 && <span className="text-red-500">-{deletions}</span>}
|
||||||
)}
|
|
||||||
{deletions > 0 && (
|
|
||||||
<span className="text-red-500">-{deletions}</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
@@ -229,11 +254,9 @@ const FileModifiedDropdown = memo(({
|
|||||||
<div className="border-t border-bolt-elements-borderColor p-2">
|
<div className="border-t border-bolt-elements-borderColor p-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard.writeText(filteredFiles.map(([filePath]) => filePath).join('\n'));
|
||||||
filteredFiles.map(([filePath]) => filePath).join('\n')
|
|
||||||
);
|
|
||||||
toast('File list copied to clipboard', {
|
toast('File list copied to clipboard', {
|
||||||
icon: <div className="i-ph:check-circle text-accent-500" />
|
icon: <div className="i-ph:check-circle text-accent-500" />,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
className="w-full flex items-center justify-center gap-2 px-3 py-1.5 text-sm rounded-lg bg-bolt-elements-background-depth-1 hover:bg-bolt-elements-background-depth-3 transition-colors text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary"
|
className="w-full flex items-center justify-center gap-2 px-3 py-1.5 text-sm rounded-lg bg-bolt-elements-background-depth-1 hover:bg-bolt-elements-background-depth-3 transition-colors text-bolt-elements-textTertiary hover:text-bolt-elements-textPrimary"
|
||||||
@@ -249,22 +272,18 @@ const FileModifiedDropdown = memo(({
|
|||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export const Workbench = memo(({
|
export const Workbench = memo(
|
||||||
chatStarted,
|
({ chatStarted, isStreaming, actionRunner, metadata, updateChatMestaData }: WorkspaceProps) => {
|
||||||
isStreaming,
|
|
||||||
actionRunner,
|
|
||||||
metadata,
|
|
||||||
updateChatMestaData
|
|
||||||
}: WorkspaceProps) => {
|
|
||||||
renderLogger.trace('Workbench');
|
renderLogger.trace('Workbench');
|
||||||
|
|
||||||
const [isSyncing, setIsSyncing] = useState(false);
|
const [isSyncing, setIsSyncing] = useState(false);
|
||||||
const [isPushDialogOpen, setIsPushDialogOpen] = useState(false);
|
const [isPushDialogOpen, setIsPushDialogOpen] = useState(false);
|
||||||
const [fileHistory, setFileHistory] = useState<Record<string, FileHistory>>({});
|
const [fileHistory, setFileHistory] = useState<Record<string, FileHistory>>({});
|
||||||
|
|
||||||
const modifiedFiles = Array.from(useStore(workbenchStore.unsavedFiles).keys());
|
// const modifiedFiles = Array.from(useStore(workbenchStore.unsavedFiles).keys());
|
||||||
|
|
||||||
const hasPreview = useStore(computed(workbenchStore.previews, (previews) => previews.length > 0));
|
const hasPreview = useStore(computed(workbenchStore.previews, (previews) => previews.length > 0));
|
||||||
const showWorkbench = useStore(workbenchStore.showWorkbench);
|
const showWorkbench = useStore(workbenchStore.showWorkbench);
|
||||||
@@ -387,10 +406,7 @@ export const Workbench = memo(({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{selectedView === 'diff' && (
|
{selectedView === 'diff' && (
|
||||||
<FileModifiedDropdown
|
<FileModifiedDropdown fileHistory={fileHistory} onSelectFile={handleSelectFile} />
|
||||||
fileHistory={fileHistory}
|
|
||||||
onSelectFile={handleSelectFile}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
<IconButton
|
<IconButton
|
||||||
icon="i-ph:x-circle"
|
icon="i-ph:x-circle"
|
||||||
@@ -402,10 +418,7 @@ export const Workbench = memo(({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex-1 overflow-hidden">
|
<div className="relative flex-1 overflow-hidden">
|
||||||
<View
|
<View initial={{ x: '0%' }} animate={{ x: selectedView === 'code' ? '0%' : '-100%' }}>
|
||||||
initial={{ x: '0%' }}
|
|
||||||
animate={{ x: selectedView === 'code' ? '0%' : '-100%' }}
|
|
||||||
>
|
|
||||||
<EditorPanel
|
<EditorPanel
|
||||||
editorDocument={currentDocument}
|
editorDocument={currentDocument}
|
||||||
isStreaming={isStreaming}
|
isStreaming={isStreaming}
|
||||||
@@ -424,16 +437,9 @@ export const Workbench = memo(({
|
|||||||
initial={{ x: '100%' }}
|
initial={{ x: '100%' }}
|
||||||
animate={{ x: selectedView === 'diff' ? '0%' : selectedView === 'code' ? '100%' : '-100%' }}
|
animate={{ x: selectedView === 'diff' ? '0%' : selectedView === 'code' ? '100%' : '-100%' }}
|
||||||
>
|
>
|
||||||
<DiffView
|
<DiffView fileHistory={fileHistory} setFileHistory={setFileHistory} actionRunner={actionRunner} />
|
||||||
fileHistory={fileHistory}
|
|
||||||
setFileHistory={setFileHistory}
|
|
||||||
actionRunner={actionRunner}
|
|
||||||
/>
|
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View initial={{ x: '100%' }} animate={{ x: selectedView === 'preview' ? '0%' : '100%' }}>
|
||||||
initial={{ x: '100%' }}
|
|
||||||
animate={{ x: selectedView === 'preview' ? '0%' : '100%' }}
|
|
||||||
>
|
|
||||||
<Preview />
|
<Preview />
|
||||||
</View>
|
</View>
|
||||||
</div>
|
</div>
|
||||||
@@ -447,6 +453,7 @@ export const Workbench = memo(({
|
|||||||
try {
|
try {
|
||||||
const commitMessage = prompt('Please enter a commit message:', 'Initial commit') || 'Initial commit';
|
const commitMessage = prompt('Please enter a commit message:', 'Initial commit') || 'Initial commit';
|
||||||
await workbenchStore.pushToGitHub(repoName, commitMessage, username, token);
|
await workbenchStore.pushToGitHub(repoName, commitMessage, username, token);
|
||||||
|
|
||||||
const repoUrl = `https://github.com/${username}/${repoName}`;
|
const repoUrl = `https://github.com/${username}/${repoName}`;
|
||||||
|
|
||||||
if (updateChatMestaData && !metadata?.gitUrl) {
|
if (updateChatMestaData && !metadata?.gitUrl) {
|
||||||
@@ -467,7 +474,8 @@ export const Workbench = memo(({
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// View component for rendering content with motion transitions
|
// View component for rendering content with motion transitions
|
||||||
interface ViewProps extends HTMLMotionProps<'div'> {
|
interface ViewProps extends HTMLMotionProps<'div'> {
|
||||||
|
|||||||
@@ -204,7 +204,10 @@ export async function selectContext(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!filePaths.includes(fullPath)) {
|
if (!filePaths.includes(fullPath)) {
|
||||||
throw new Error(`File ${path} is not in the list of files above.`);
|
logger.error(`File ${path} is not in the list of files above.`);
|
||||||
|
return;
|
||||||
|
|
||||||
|
// throw new Error(`File ${path} is not in the list of files above.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currrentFiles.includes(path)) {
|
if (currrentFiles.includes(path)) {
|
||||||
@@ -218,6 +221,13 @@ export async function selectContext(props: {
|
|||||||
onFinish(resp);
|
onFinish(resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const totalFiles = Object.keys(filteredFiles).length;
|
||||||
|
logger.info(`Total files: ${totalFiles}`);
|
||||||
|
|
||||||
|
if (totalFiles == 0) {
|
||||||
|
throw new Error(`Bolt failed to select files`);
|
||||||
|
}
|
||||||
|
|
||||||
return filteredFiles;
|
return filteredFiles;
|
||||||
|
|
||||||
// generateText({
|
// generateText({
|
||||||
|
|||||||
@@ -319,21 +319,23 @@ export class ActionRunner {
|
|||||||
const webcontainer = await this.#webcontainer;
|
const webcontainer = await this.#webcontainer;
|
||||||
const historyPath = this.#getHistoryPath(filePath);
|
const historyPath = this.#getHistoryPath(filePath);
|
||||||
const content = await webcontainer.fs.readFile(historyPath, 'utf-8');
|
const content = await webcontainer.fs.readFile(historyPath, 'utf-8');
|
||||||
|
|
||||||
return JSON.parse(content);
|
return JSON.parse(content);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.error('Failed to get file history:', error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveFileHistory(filePath: string, history: FileHistory) {
|
async saveFileHistory(filePath: string, history: FileHistory) {
|
||||||
const webcontainer = await this.#webcontainer;
|
// const webcontainer = await this.#webcontainer;
|
||||||
const historyPath = this.#getHistoryPath(filePath);
|
const historyPath = this.#getHistoryPath(filePath);
|
||||||
|
|
||||||
await this.#runFileAction({
|
await this.#runFileAction({
|
||||||
type: 'file',
|
type: 'file',
|
||||||
filePath: historyPath,
|
filePath: historyPath,
|
||||||
content: JSON.stringify(history),
|
content: JSON.stringify(history),
|
||||||
changeSource: 'auto-save'
|
changeSource: 'auto-save',
|
||||||
} as any);
|
} as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ import { streamText } from '~/lib/.server/llm/stream-text';
|
|||||||
import { stripIndents } from '~/utils/stripIndent';
|
import { stripIndents } from '~/utils/stripIndent';
|
||||||
import type { ProviderInfo } from '~/types/model';
|
import type { ProviderInfo } from '~/types/model';
|
||||||
import { getApiKeysFromCookie, getProviderSettingsFromCookie } from '~/lib/api/cookies';
|
import { getApiKeysFromCookie, getProviderSettingsFromCookie } from '~/lib/api/cookies';
|
||||||
|
import { createScopedLogger } from '~/utils/logger';
|
||||||
|
|
||||||
export async function action(args: ActionFunctionArgs) {
|
export async function action(args: ActionFunctionArgs) {
|
||||||
return enhancerAction(args);
|
return enhancerAction(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const logger = createScopedLogger('api.enhancher');
|
||||||
|
|
||||||
async function enhancerAction({ context, request }: ActionFunctionArgs) {
|
async function enhancerAction({ context, request }: ActionFunctionArgs) {
|
||||||
const { message, model, provider } = await request.json<{
|
const { message, model, provider } = await request.json<{
|
||||||
message: string;
|
message: string;
|
||||||
@@ -77,8 +80,32 @@ async function enhancerAction({ context, request }: ActionFunctionArgs) {
|
|||||||
env: context.cloudflare?.env as any,
|
env: context.cloudflare?.env as any,
|
||||||
apiKeys,
|
apiKeys,
|
||||||
providerSettings,
|
providerSettings,
|
||||||
|
options: {
|
||||||
|
system:
|
||||||
|
'You are a senior software principal architect, you should help the user analyse the user query and enrich it with the necessary context and constraints to make it more specific, actionable, and effective. You should also ensure that the prompt is self-contained and uses professional language. Your response should ONLY contain the enhanced prompt text. Do not include any explanations, metadata, or wrapper tags.',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* onError: (event) => {
|
||||||
|
* throw new Response(null, {
|
||||||
|
* status: 500,
|
||||||
|
* statusText: 'Internal Server Error',
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
for await (const part of result.fullStream) {
|
||||||
|
if (part.type === 'error') {
|
||||||
|
const error: any = part.error;
|
||||||
|
logger.error(error);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
return new Response(result.textStream, {
|
return new Response(result.textStream, {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export interface FileHistory {
|
|||||||
timestamp: number;
|
timestamp: number;
|
||||||
content: string;
|
content: string;
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
// Novo campo para rastrear a origem das mudanças
|
// Novo campo para rastrear a origem das mudanças
|
||||||
changeSource?: 'user' | 'auto-save' | 'external';
|
changeSource?: 'user' | 'auto-save' | 'external';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
export const getLanguageFromExtension = (ext: string): string => {
|
export const getLanguageFromExtension = (ext: string): string => {
|
||||||
const map: Record<string, string> = {
|
const map: Record<string, string> = {
|
||||||
js: "javascript",
|
js: 'javascript',
|
||||||
jsx: "jsx",
|
jsx: 'jsx',
|
||||||
ts: "typescript",
|
ts: 'typescript',
|
||||||
tsx: "tsx",
|
tsx: 'tsx',
|
||||||
json: "json",
|
json: 'json',
|
||||||
html: "html",
|
html: 'html',
|
||||||
css: "css",
|
css: 'css',
|
||||||
py: "python",
|
py: 'python',
|
||||||
java: "java",
|
java: 'java',
|
||||||
rb: "ruby",
|
rb: 'ruby',
|
||||||
cpp: "cpp",
|
cpp: 'cpp',
|
||||||
c: "c",
|
c: 'c',
|
||||||
cs: "csharp",
|
cs: 'csharp',
|
||||||
go: "go",
|
go: 'go',
|
||||||
rs: "rust",
|
rs: 'rust',
|
||||||
php: "php",
|
php: 'php',
|
||||||
swift: "swift",
|
swift: 'swift',
|
||||||
md: "plaintext",
|
md: 'plaintext',
|
||||||
sh: "bash",
|
sh: 'bash',
|
||||||
};
|
};
|
||||||
return map[ext] || "typescript";
|
return map[ext] || 'typescript';
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user