fix: overflow issue and file refetch

This commit is contained in:
Vukasin 2025-05-29 23:06:07 +02:00
parent c14a2e4cb4
commit 65fa837ae1
5 changed files with 160 additions and 86 deletions

View File

@ -417,7 +417,8 @@
margin-top: 0.5em;
margin-bottom: 0.5em;
padding: 0.75em 1em;
background-color: theme('colors.slate.100');
/* background 95 */
background-color: theme('colors.background/95');
border-radius: 0.375rem;
overflow-x: auto;
font-family: var(--font-mono);
@ -439,7 +440,7 @@
padding: 0.2em 0.4em;
font-size: 0.85em;
font-family: var(--font-mono);
background-color: theme('colors.slate.100');
background-color: theme('colors.background/95');
border-radius: 3px;
word-break: break-word;
}
@ -478,14 +479,14 @@
.dark & {
/* Code blocks in dark mode */
& pre {
background-color: theme('colors.zinc.800');
border: 1px solid theme('colors.zinc.700');
background-color: theme('colors.background/95');
/* border: 1px solid theme('colors.zinc.700'); */
}
& code:not([class*='language-']) {
background-color: theme('colors.zinc.800');
background-color: theme('colors.background/95');
color: theme('colors.zinc.200');
border: 1px solid theme('colors.zinc.700');
/* border: 1px solid theme('colors.zinc.700'); */
}
/* Tables in dark mode */

View File

@ -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"
onScroll={handleScroll}
>
<div className="mx-auto max-w-3xl">
<div className="mx-auto max-w-3xl min-w-0">
{displayMessages.length === 0 && !streamingTextContent && !streamingToolCall &&
!streamingText && !currentToolCall && agentStatus === 'idle' ? (
<div className="flex h-full items-center justify-center">
@ -271,7 +271,7 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
</div>
</div>
) : (
<div className="space-y-8">
<div className="space-y-8 min-w-0">
{(() => {
type MessageGroup = {
@ -379,8 +379,8 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
if (debugMode) {
return (
<div key={group.key} className="flex justify-end">
<div className="inline-flex max-w-[85%] rounded-xl bg-primary/10 px-4 py-3">
<pre className="text-xs font-mono whitespace-pre-wrap overflow-x-auto">
<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 min-w-0 flex-1">
{message.content}
</pre>
</div>
@ -402,10 +402,10 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
return (
<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="space-y-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 min-w-0 flex-1">
{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 */}
@ -422,8 +422,8 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
<KortixLogo />
</div>
<div className="flex-1">
<div className="inline-flex max-w-[90%] rounded-lg px-4 text-sm">
<div className="space-y-2">
<div className="flex max-w-[90%] rounded-lg px-4 text-sm break-words overflow-hidden">
<div className="space-y-2 min-w-0 flex-1">
{(() => {
// In debug mode, just show raw messages content
if (debugMode) {
@ -487,7 +487,7 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
elements.push(
<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}
</div>
</div>
@ -534,7 +534,7 @@ export const ThreadContent: React.FC<ThreadContentProps> = ({
return (
<>
{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 && (
<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 && (
<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 && (
<span className="inline-block h-4 w-0.5 bg-primary ml-0.5 -mb-1 animate-pulse" />

View File

@ -12,7 +12,7 @@ export type CodeBlockProps = {
function CodeBlock({ children, className, ...props }: CodeBlockProps) {
return (
<div className={cn(className)} {...props}>
<div className={cn('w-px flex-grow min-w-0 overflow-hidden flex', className)} {...props}>
{children}
</div>
);
@ -41,6 +41,10 @@ function CodeBlockCode({
useEffect(() => {
async function highlight() {
if (!code || typeof code !== 'string') {
setHighlightedHtml(null);
return;
}
const html = await codeToHtml(code, {
lang: language,
theme,
@ -60,7 +64,7 @@ function CodeBlockCode({
highlight();
}, [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
return highlightedHtml ? (
@ -71,7 +75,7 @@ function CodeBlockCode({
/>
) : (
<div className={classNames} {...props}>
<pre>
<pre className="!overflow-x-auto !w-px !flex-grow !min-w-0 !box-border">
<code>{code}</code>
</pre>
</div>

View File

@ -47,11 +47,11 @@ const INITIAL_COMPONENTS: Partial<Components> = {
const language = extractLanguage(className);
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
code={children as string}
language={language}
className="text-sm overflow-x-auto"
className="text-sm"
/>
</CodeBlock>
);

View File

@ -128,16 +128,34 @@ export function useCachedFile<T = string>(
url.searchParams.append('path', normalizedPath);
// Fetch with authentication
const attemptFetch = async (isRetry: boolean = false): Promise<Response> => {
const response = await fetch(url.toString(), {
headers: {
'Authorization': `Bearer ${session?.access_token}`,
},
'Authorization': `Bearer ${session?.access_token}`
}
});
if (!response.ok) {
throw new Error(`Failed to load file: ${response.status} ${response.statusText}`);
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;
};
const response = await attemptFetch();
// Process content based on contentType
let content;
let cacheType: 'content' | 'url' | 'error' = 'content';
@ -502,13 +520,32 @@ export const FileCache = {
// Properly encode the path parameter for UTF-8 support
url.searchParams.append('path', normalizedPath);
const attemptFetch = async (isRetry: boolean = false): Promise<Response> => {
const response = await fetch(url.toString(), {
headers: {
'Authorization': `Bearer ${token}`
},
});
if (!response.ok) throw new Error(`Failed to preload file: ${response.status}`);
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;
};
const response = await attemptFetch();
// Determine how to process the content based on file type
const extension = path.split('.').pop()?.toLowerCase();
@ -623,6 +660,7 @@ export async function getCachedFile(
console.log(`[FILE CACHE] Fetching file: ${url.toString()}`);
const attemptFetch = async (isRetry: boolean = false): Promise<Response> => {
const response = await fetch(url.toString(), {
headers: {
'Authorization': `Bearer ${options.token}`
@ -630,10 +668,26 @@ export async function getCachedFile(
});
if (!response.ok) {
console.error(`[FILE CACHE] Failed response for ${normalizedPath}: Status ${response.status}`);
throw new Error(`Failed to load file: ${response.status} ${response.statusText}`);
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;
};
const response = await attemptFetch();
// Process content based on type
let content;
@ -688,6 +742,7 @@ export async function fetchFileContent(
const requestId = Math.random().toString(36).substring(2, 9);
console.log(`[FILE CACHE] Fetching fresh content for ${sandboxId}:${filePath}`);
const attemptFetch = async (isRetry: boolean = false): Promise<string | Blob | any> => {
try {
// Prepare the API URL
const apiUrl = `${process.env.NEXT_PUBLIC_BACKEND_URL}/sandboxes/${sandboxId}/files/content`;
@ -733,6 +788,20 @@ export async function fetchFileContent(
} 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) {
console.error(`[FILE CACHE] Error fetching file content:`, error);
throw error;