mirror of https://github.com/kortix-ai/suna.git
fix: overflow issue and file refetch
This commit is contained in:
parent
c14a2e4cb4
commit
65fa837ae1
|
@ -417,7 +417,8 @@
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
padding: 0.75em 1em;
|
padding: 0.75em 1em;
|
||||||
background-color: theme('colors.slate.100');
|
/* background 95 */
|
||||||
|
background-color: theme('colors.background/95');
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
|
@ -439,7 +440,7 @@
|
||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
background-color: theme('colors.slate.100');
|
background-color: theme('colors.background/95');
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
@ -478,14 +479,14 @@
|
||||||
.dark & {
|
.dark & {
|
||||||
/* Code blocks in dark mode */
|
/* Code blocks in dark mode */
|
||||||
& pre {
|
& pre {
|
||||||
background-color: theme('colors.zinc.800');
|
background-color: theme('colors.background/95');
|
||||||
border: 1px solid theme('colors.zinc.700');
|
/* border: 1px solid theme('colors.zinc.700'); */
|
||||||
}
|
}
|
||||||
|
|
||||||
& code:not([class*='language-']) {
|
& code:not([class*='language-']) {
|
||||||
background-color: theme('colors.zinc.800');
|
background-color: theme('colors.background/95');
|
||||||
color: theme('colors.zinc.200');
|
color: theme('colors.zinc.200');
|
||||||
border: 1px solid theme('colors.zinc.700');
|
/* border: 1px solid theme('colors.zinc.700'); */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tables in dark mode */
|
/* Tables in dark mode */
|
||||||
|
|
|
@ -262,7 +262,7 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
|
||||||
className="flex-1 overflow-y-auto scrollbar-thin scrollbar-track-secondary/0 scrollbar-thumb-primary/10 scrollbar-thumb-rounded-full hover:scrollbar-thumb-primary/10 px-6 py-4 pb-72 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"
|
className="flex-1 overflow-y-auto scrollbar-thin scrollbar-track-secondary/0 scrollbar-thumb-primary/10 scrollbar-thumb-rounded-full hover:scrollbar-thumb-primary/10 px-6 py-4 pb-72 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
>
|
>
|
||||||
<div className="mx-auto max-w-3xl">
|
<div className="mx-auto max-w-3xl min-w-0">
|
||||||
{displayMessages.length === 0 && !streamingTextContent && !streamingToolCall &&
|
{displayMessages.length === 0 && !streamingTextContent && !streamingToolCall &&
|
||||||
!streamingText && !currentToolCall && agentStatus === 'idle' ? (
|
!streamingText && !currentToolCall && agentStatus === 'idle' ? (
|
||||||
<div className="flex h-full items-center justify-center">
|
<div className="flex h-full items-center justify-center">
|
||||||
|
@ -271,7 +271,7 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8 min-w-0">
|
||||||
{(() => {
|
{(() => {
|
||||||
|
|
||||||
type MessageGroup = {
|
type MessageGroup = {
|
||||||
|
@ -379,8 +379,8 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
return (
|
return (
|
||||||
<div key={group.key} className="flex justify-end">
|
<div key={group.key} className="flex justify-end">
|
||||||
<div className="inline-flex max-w-[85%] rounded-xl bg-primary/10 px-4 py-3">
|
<div className="flex max-w-[85%] rounded-xl bg-primary/10 px-4 py-3 break-words overflow-hidden">
|
||||||
<pre className="text-xs font-mono whitespace-pre-wrap overflow-x-auto">
|
<pre className="text-xs font-mono whitespace-pre-wrap overflow-x-auto min-w-0 flex-1">
|
||||||
{message.content}
|
{message.content}
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
|
@ -402,10 +402,10 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={group.key} className="flex justify-end">
|
<div key={group.key} className="flex justify-end">
|
||||||
<div className="inline-flex max-w-[85%] rounded-xl bg-primary/10 px-4 py-3">
|
<div className="flex max-w-[85%] rounded-xl bg-primary/10 px-4 py-3 break-words overflow-hidden">
|
||||||
<div className="space-y-3">
|
<div className="space-y-3 min-w-0 flex-1">
|
||||||
{cleanContent && (
|
{cleanContent && (
|
||||||
<Markdown className="text-sm prose prose-sm dark:prose-invert chat-markdown max-w-none [&>:first-child]:mt-0 prose-headings:mt-3">{cleanContent}</Markdown>
|
<Markdown className="text-sm prose prose-sm dark:prose-invert chat-markdown max-w-none [&>:first-child]:mt-0 prose-headings:mt-3 break-words overflow-wrap-anywhere">{cleanContent}</Markdown>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Use the helper function to render user attachments */}
|
{/* Use the helper function to render user attachments */}
|
||||||
|
@ -422,8 +422,8 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
|
||||||
<KortixLogo />
|
<KortixLogo />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="inline-flex max-w-[90%] rounded-lg px-4 text-sm">
|
<div className="flex max-w-[90%] rounded-lg px-4 text-sm break-words overflow-hidden">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2 min-w-0 flex-1">
|
||||||
{(() => {
|
{(() => {
|
||||||
// In debug mode, just show raw messages content
|
// In debug mode, just show raw messages content
|
||||||
if (debugMode) {
|
if (debugMode) {
|
||||||
|
@ -487,7 +487,7 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
|
||||||
|
|
||||||
elements.push(
|
elements.push(
|
||||||
<div key={msgKey} className={assistantMessageCount > 0 ? "mt-2" : ""}>
|
<div key={msgKey} className={assistantMessageCount > 0 ? "mt-2" : ""}>
|
||||||
<div className="prose prose-sm dark:prose-invert chat-markdown max-w-none [&>:first-child]:mt-0 prose-headings:mt-3">
|
<div className="prose prose-sm dark:prose-invert chat-markdown max-w-none [&>:first-child]:mt-0 prose-headings:mt-3 break-words overflow-hidden">
|
||||||
{renderedContent}
|
{renderedContent}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -534,7 +534,7 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{textBeforeTag && (
|
{textBeforeTag && (
|
||||||
<Markdown className="text-sm prose prose-sm dark:prose-invert chat-markdown max-w-none [&>:first-child]:mt-0 prose-headings:mt-3">{textBeforeTag}</Markdown>
|
<Markdown className="text-sm prose prose-sm dark:prose-invert chat-markdown max-w-none [&>:first-child]:mt-0 prose-headings:mt-3 break-words overflow-wrap-anywhere">{textBeforeTag}</Markdown>
|
||||||
)}
|
)}
|
||||||
{showCursor && (
|
{showCursor && (
|
||||||
<span className="inline-block h-4 w-0.5 bg-primary ml-0.5 -mb-1 animate-pulse" />
|
<span className="inline-block h-4 w-0.5 bg-primary ml-0.5 -mb-1 animate-pulse" />
|
||||||
|
@ -611,7 +611,7 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{textBeforeTag && (
|
{textBeforeTag && (
|
||||||
<Markdown className="text-sm prose prose-sm dark:prose-invert chat-markdown max-w-none [&>:first-child]:mt-0 prose-headings:mt-3">{textBeforeTag}</Markdown>
|
<Markdown className="text-sm prose prose-sm dark:prose-invert chat-markdown max-w-none [&>:first-child]:mt-0 prose-headings:mt-3 break-words overflow-wrap-anywhere">{textBeforeTag}</Markdown>
|
||||||
)}
|
)}
|
||||||
{showCursor && (
|
{showCursor && (
|
||||||
<span className="inline-block h-4 w-0.5 bg-primary ml-0.5 -mb-1 animate-pulse" />
|
<span className="inline-block h-4 w-0.5 bg-primary ml-0.5 -mb-1 animate-pulse" />
|
||||||
|
|
|
@ -12,7 +12,7 @@ export type CodeBlockProps = {
|
||||||
|
|
||||||
function CodeBlock({ children, className, ...props }: CodeBlockProps) {
|
function CodeBlock({ children, className, ...props }: CodeBlockProps) {
|
||||||
return (
|
return (
|
||||||
<div className={cn(className)} {...props}>
|
<div className={cn('w-px flex-grow min-w-0 overflow-hidden flex', className)} {...props}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -41,6 +41,10 @@ function CodeBlockCode({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function highlight() {
|
async function highlight() {
|
||||||
|
if (!code || typeof code !== 'string') {
|
||||||
|
setHighlightedHtml(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const html = await codeToHtml(code, {
|
const html = await codeToHtml(code, {
|
||||||
lang: language,
|
lang: language,
|
||||||
theme,
|
theme,
|
||||||
|
@ -60,7 +64,7 @@ function CodeBlockCode({
|
||||||
highlight();
|
highlight();
|
||||||
}, [code, language, theme]);
|
}, [code, language, theme]);
|
||||||
|
|
||||||
const classNames = cn('[&_pre]:!bg-background/95 [&_pre]:rounded-lg [&_pre]:p-4', className);
|
const classNames = cn('[&_pre]:!bg-background/95 [&_pre]:rounded-lg [&_pre]:p-4 [&_pre]:!overflow-x-auto [&_pre]:!w-px [&_pre]:!flex-grow [&_pre]:!min-w-0 [&_pre]:!box-border [&_.shiki]:!overflow-x-auto [&_.shiki]:!w-px [&_.shiki]:!flex-grow [&_.shiki]:!min-w-0 [&_code]:!min-w-0 [&_code]:!whitespace-pre', 'w-px flex-grow min-w-0 overflow-hidden flex w-full', className);
|
||||||
|
|
||||||
// SSR fallback: render plain code if not hydrated yet
|
// SSR fallback: render plain code if not hydrated yet
|
||||||
return highlightedHtml ? (
|
return highlightedHtml ? (
|
||||||
|
@ -71,7 +75,7 @@ function CodeBlockCode({
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className={classNames} {...props}>
|
<div className={classNames} {...props}>
|
||||||
<pre>
|
<pre className="!overflow-x-auto !w-px !flex-grow !min-w-0 !box-border">
|
||||||
<code>{code}</code>
|
<code>{code}</code>
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -47,11 +47,11 @@ const INITIAL_COMPONENTS: Partial<Components> = {
|
||||||
const language = extractLanguage(className);
|
const language = extractLanguage(className);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CodeBlock className="rounded-md overflow-hidden my-4 border border-zinc-200 dark:border-zinc-800">
|
<CodeBlock className="rounded-md overflow-hidden my-4 border border-zinc-200 dark:border-zinc-800 max-w-full min-w-0 w-full">
|
||||||
<CodeBlockCode
|
<CodeBlockCode
|
||||||
code={children as string}
|
code={children as string}
|
||||||
language={language}
|
language={language}
|
||||||
className="text-sm overflow-x-auto"
|
className="text-sm"
|
||||||
/>
|
/>
|
||||||
</CodeBlock>
|
</CodeBlock>
|
||||||
);
|
);
|
||||||
|
|
|
@ -128,15 +128,33 @@ export function useCachedFile<T = string>(
|
||||||
url.searchParams.append('path', normalizedPath);
|
url.searchParams.append('path', normalizedPath);
|
||||||
|
|
||||||
// Fetch with authentication
|
// Fetch with authentication
|
||||||
const response = await fetch(url.toString(), {
|
const attemptFetch = async (isRetry: boolean = false): Promise<Response> => {
|
||||||
headers: {
|
const response = await fetch(url.toString(), {
|
||||||
'Authorization': `Bearer ${session?.access_token}`,
|
headers: {
|
||||||
},
|
'Authorization': `Bearer ${session?.access_token}`
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const responseText = await response.text();
|
||||||
|
const errorMessage = `Failed to load file: ${response.status} ${response.statusText}`;
|
||||||
|
|
||||||
|
// Check if this is a workspace initialization error and we haven't retried yet
|
||||||
|
const isWorkspaceNotRunning = responseText.includes('Workspace is not running');
|
||||||
|
if (isWorkspaceNotRunning && !isRetry) {
|
||||||
|
console.log(`[FILE CACHE] Workspace not ready, retrying in 2s for ${normalizedPath}`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
return attemptFetch(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`[FILE CACHE] Failed response for ${normalizedPath}: Status ${response.status}`);
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
if (!response.ok) {
|
const response = await attemptFetch();
|
||||||
throw new Error(`Failed to load file: ${response.status} ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process content based on contentType
|
// Process content based on contentType
|
||||||
let content;
|
let content;
|
||||||
|
@ -502,13 +520,32 @@ export const FileCache = {
|
||||||
// Properly encode the path parameter for UTF-8 support
|
// Properly encode the path parameter for UTF-8 support
|
||||||
url.searchParams.append('path', normalizedPath);
|
url.searchParams.append('path', normalizedPath);
|
||||||
|
|
||||||
const response = await fetch(url.toString(), {
|
const attemptFetch = async (isRetry: boolean = false): Promise<Response> => {
|
||||||
headers: {
|
const response = await fetch(url.toString(), {
|
||||||
'Authorization': `Bearer ${token}`
|
headers: {
|
||||||
},
|
'Authorization': `Bearer ${token}`
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const responseText = await response.text();
|
||||||
|
const errorMessage = `Failed to preload file: ${response.status}`;
|
||||||
|
|
||||||
|
// Check if this is a workspace initialization error and we haven't retried yet
|
||||||
|
const isWorkspaceNotRunning = responseText.includes('Workspace is not running');
|
||||||
|
if (isWorkspaceNotRunning && !isRetry) {
|
||||||
|
console.log(`[FILE CACHE] Workspace not ready during preload, retrying in 2s for ${normalizedPath}`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
return attemptFetch(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
if (!response.ok) throw new Error(`Failed to preload file: ${response.status}`);
|
const response = await attemptFetch();
|
||||||
|
|
||||||
// Determine how to process the content based on file type
|
// Determine how to process the content based on file type
|
||||||
const extension = path.split('.').pop()?.toLowerCase();
|
const extension = path.split('.').pop()?.toLowerCase();
|
||||||
|
@ -623,16 +660,33 @@ export async function getCachedFile(
|
||||||
|
|
||||||
console.log(`[FILE CACHE] Fetching file: ${url.toString()}`);
|
console.log(`[FILE CACHE] Fetching file: ${url.toString()}`);
|
||||||
|
|
||||||
const response = await fetch(url.toString(), {
|
const attemptFetch = async (isRetry: boolean = false): Promise<Response> => {
|
||||||
headers: {
|
const response = await fetch(url.toString(), {
|
||||||
'Authorization': `Bearer ${options.token}`
|
headers: {
|
||||||
|
'Authorization': `Bearer ${options.token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const responseText = await response.text();
|
||||||
|
const errorMessage = `Failed to load file: ${response.status} ${response.statusText}`;
|
||||||
|
|
||||||
|
// Check if this is a workspace initialization error and we haven't retried yet
|
||||||
|
const isWorkspaceNotRunning = responseText.includes('Workspace is not running');
|
||||||
|
if (isWorkspaceNotRunning && !isRetry) {
|
||||||
|
console.log(`[FILE CACHE] Workspace not ready, retrying in 2s for ${normalizedPath}`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
return attemptFetch(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error(`[FILE CACHE] Failed response for ${normalizedPath}: Status ${response.status}`);
|
||||||
|
throw new Error(errorMessage);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
if (!response.ok) {
|
const response = await attemptFetch();
|
||||||
console.error(`[FILE CACHE] Failed response for ${normalizedPath}: Status ${response.status}`);
|
|
||||||
throw new Error(`Failed to load file: ${response.status} ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process content based on type
|
// Process content based on type
|
||||||
let content;
|
let content;
|
||||||
|
@ -688,51 +742,66 @@ export async function fetchFileContent(
|
||||||
const requestId = Math.random().toString(36).substring(2, 9);
|
const requestId = Math.random().toString(36).substring(2, 9);
|
||||||
console.log(`[FILE CACHE] Fetching fresh content for ${sandboxId}:${filePath}`);
|
console.log(`[FILE CACHE] Fetching fresh content for ${sandboxId}:${filePath}`);
|
||||||
|
|
||||||
try {
|
const attemptFetch = async (isRetry: boolean = false): Promise<string | Blob | any> => {
|
||||||
// Prepare the API URL
|
try {
|
||||||
const apiUrl = `${process.env.NEXT_PUBLIC_BACKEND_URL}/sandboxes/${sandboxId}/files/content`;
|
// Prepare the API URL
|
||||||
const url = new URL(apiUrl);
|
const apiUrl = `${process.env.NEXT_PUBLIC_BACKEND_URL}/sandboxes/${sandboxId}/files/content`;
|
||||||
url.searchParams.append('path', filePath);
|
const url = new URL(apiUrl);
|
||||||
|
url.searchParams.append('path', filePath);
|
||||||
// Set up fetch options
|
|
||||||
const fetchOptions: RequestInit = {
|
|
||||||
method: 'GET',
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Execute fetch
|
|
||||||
const response = await fetch(url.toString(), fetchOptions);
|
|
||||||
if (!response.ok) {
|
|
||||||
const errorText = await response.text();
|
|
||||||
throw new Error(`Failed to fetch file content: ${response.status} ${errorText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// CRITICAL: Detect correct response handling based on file type
|
|
||||||
// Excel files, PDFs and other binary documents should be handled as blobs
|
|
||||||
const extension = filePath.split('.').pop()?.toLowerCase();
|
|
||||||
const isBinaryFile = ['xlsx', 'xls', 'docx', 'doc', 'pptx', 'ppt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'zip'].includes(extension || '');
|
|
||||||
|
|
||||||
// Handle response based on content type
|
|
||||||
if (contentType === 'blob' || isBinaryFile) {
|
|
||||||
const blob = await response.blob();
|
|
||||||
|
|
||||||
// Set correct MIME type for known file types
|
// Set up fetch options
|
||||||
if (extension) {
|
const fetchOptions: RequestInit = {
|
||||||
const mimeType = FileCache.getMimeType(filePath);
|
method: 'GET',
|
||||||
if (mimeType && mimeType !== blob.type) {
|
headers: {
|
||||||
// Create a new blob with correct type
|
Authorization: `Bearer ${token}`,
|
||||||
return new Blob([blob], { type: mimeType });
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// Execute fetch
|
||||||
|
const response = await fetch(url.toString(), fetchOptions);
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(`Failed to fetch file content: ${response.status} ${errorText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return blob;
|
// CRITICAL: Detect correct response handling based on file type
|
||||||
} else if (contentType === 'json') {
|
// Excel files, PDFs and other binary documents should be handled as blobs
|
||||||
return await response.json();
|
const extension = filePath.split('.').pop()?.toLowerCase();
|
||||||
} else {
|
const isBinaryFile = ['xlsx', 'xls', 'docx', 'doc', 'pptx', 'ppt', 'pdf', 'png', 'jpg', 'jpeg', 'gif', 'zip'].includes(extension || '');
|
||||||
return await response.text();
|
|
||||||
|
// Handle response based on content type
|
||||||
|
if (contentType === 'blob' || isBinaryFile) {
|
||||||
|
const blob = await response.blob();
|
||||||
|
|
||||||
|
// Set correct MIME type for known file types
|
||||||
|
if (extension) {
|
||||||
|
const mimeType = FileCache.getMimeType(filePath);
|
||||||
|
if (mimeType && mimeType !== blob.type) {
|
||||||
|
// Create a new blob with correct type
|
||||||
|
return new Blob([blob], { type: mimeType });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return blob;
|
||||||
|
} else if (contentType === 'json') {
|
||||||
|
return await response.json();
|
||||||
|
} else {
|
||||||
|
return await response.text();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
// Check if this is a workspace initialization error and we haven't retried yet
|
||||||
|
const isWorkspaceNotRunning = error.message?.includes('Workspace is not running');
|
||||||
|
if (isWorkspaceNotRunning && !isRetry) {
|
||||||
|
console.log(`[FILE CACHE] Workspace not ready, retrying in 2s for ${filePath}`);
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
return attemptFetch(true);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
return await attemptFetch();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[FILE CACHE] Error fetching file content:`, error);
|
console.error(`[FILE CACHE] Error fetching file content:`, error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|
Loading…
Reference in New Issue