md image render & fix tool call cut off

This commit is contained in:
marko-kraemer 2025-08-26 20:17:44 -07:00
parent 14edb083e7
commit d782582d74
10 changed files with 80 additions and 19 deletions

View File

@ -200,7 +200,7 @@ export function FileRenderer({
) : fileType === 'pdf' && binaryUrl ? (
<PdfRenderer url={binaryUrl} />
) : fileType === 'markdown' ? (
<MarkdownRenderer content={content || ''} ref={markdownRef} />
<MarkdownRenderer content={content || ''} ref={markdownRef} project={project} />
) : fileType === 'csv' ? (
<CsvRenderer content={content || ''} />
) : fileType === 'xlsx' ? (

View File

@ -8,6 +8,8 @@ import rehypeSanitize from 'rehype-sanitize';
import { ScrollArea } from '@/components/ui/scroll-area';
import { cn } from '@/lib/utils';
import { CodeRenderer } from './code-renderer';
import { useImageContent } from '@/hooks/use-image-content';
import type { Project } from '@/lib/api';
// Process Unicode escape sequences in content
export const processUnicodeContent = (content: string): string => {
@ -29,15 +31,62 @@ export const processUnicodeContent = (content: string): string => {
});
};
// Authenticated image component using existing useImageContent hook
interface AuthenticatedImageProps {
src: string;
alt?: string;
className?: string;
project?: Project;
}
function AuthenticatedImage({ src, alt, className, project }: AuthenticatedImageProps) {
// For sandbox files, use the existing useImageContent hook
const sandboxId = typeof project?.sandbox === 'string'
? project.sandbox
: project?.sandbox?.id;
const { data: imageUrl, isLoading, error } = useImageContent(sandboxId, src);
// If it's already a URL or data URL, use regular img
if (src.startsWith('http') || src.startsWith('data:')) {
return <img src={src} alt={alt || ''} className={className} />;
}
if (isLoading) {
return (
<span className={cn("inline-block p-2 bg-muted/30 rounded text-xs text-muted-foreground", className)}>
Loading image...
</span>
);
}
if (error || !imageUrl) {
return (
<span className={cn("inline-block p-2 bg-muted/30 rounded border border-dashed text-xs text-muted-foreground", className)}>
Failed to load: {alt || src}
</span>
);
}
return (
<img
src={imageUrl}
alt={alt || ''}
className={className}
/>
);
}
interface MarkdownRendererProps {
content: string;
className?: string;
project?: Project;
}
export const MarkdownRenderer = forwardRef<
HTMLDivElement,
MarkdownRendererProps
>(({ content, className }, ref) => {
>(({ content, className, project }, ref) => {
// Process Unicode escape sequences in the content
const processedContent = processUnicodeContent(content);
@ -103,10 +152,11 @@ export const MarkdownRenderer = forwardRef<
/>
),
img: ({ node, ...props }) => (
<img
className="max-w-full h-auto rounded-md my-2"
{...props}
<AuthenticatedImage
src={props.src || ''}
alt={props.alt || ''}
className="max-w-full h-auto rounded-md my-2"
project={project}
/>
),
pre: ({ node, ...props }) => (

View File

@ -552,6 +552,7 @@ export function FileAttachment({
content={fileContent}
previewUrl={fileUrl}
className="h-full w-full"
project={project}
/>
)}
</>

View File

@ -8,6 +8,7 @@ import { cn } from '@/lib/utils';
interface CsvRendererProps {
content: string;
className?: string;
project?: any; // Optional to allow passing from file-attachment
}
/**

View File

@ -2,30 +2,34 @@
import React from 'react';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Markdown } from '@/components/ui/markdown';
import { cn } from '@/lib/utils';
import { MarkdownRenderer as FileMarkdownRenderer } from '@/components/file-renderers/markdown-renderer';
import type { Project } from '@/lib/api';
interface MarkdownRendererProps {
content: string;
className?: string;
project?: Project;
}
/**
* Renderer for Markdown content with scrollable container
* Now uses the FileMarkdownRenderer with image authentication support
*/
export function MarkdownRenderer({
content,
className
className,
project
}: MarkdownRendererProps) {
return (
<div className={cn('w-full h-full overflow-hidden', className)}>
<ScrollArea className="w-full h-full">
<div className="p-4">
<Markdown
<FileMarkdownRenderer
content={content}
className="prose prose-sm dark:prose-invert max-w-none [&>:first-child]:mt-0"
>
{content}
</Markdown>
project={project}
/>
</div>
</ScrollArea>
</div>

View File

@ -12,6 +12,7 @@ interface XlsxRendererProps {
className?: string;
onSheetChange?: (sheetIndex: number) => void; // Callback for sheet changes
activeSheetIndex?: number; // Controlled sheet index
project?: any; // Optional to allow passing from file-attachment
}
/**

View File

@ -893,7 +893,7 @@ export function ToolCallSidePanel({
/>
)}
<div className={`flex-1 ${currentView === 'browser' ? 'overflow-hidden' : 'overflow-auto'} scrollbar-thin scrollbar-thumb-zinc-300 dark:scrollbar-thumb-zinc-700 scrollbar-track-transparent`}>
<div className={`flex-1 ${currentView === 'browser' ? 'overflow-hidden' : 'overflow-hidden'} scrollbar-thin scrollbar-thumb-zinc-300 dark:scrollbar-thumb-zinc-700 scrollbar-track-transparent`}>
{/* Always render VNC iframe to maintain connection when available */}
{persistentVncIframe && (
<div className={`${currentView === 'browser' ? 'h-full flex flex-col' : 'hidden'}`}>
@ -1013,7 +1013,7 @@ export function ToolCallSidePanel({
overflow: 'hidden',
}}
>
<div className="flex-1 flex flex-col overflow-scroll bg-card">
<div className="flex-1 flex flex-col overflow-hidden bg-card">
{renderContent()}
</div>
{(displayTotalCalls > 1 || (isCurrentToolStreaming && totalCompletedCalls > 0)) && (

View File

@ -243,6 +243,7 @@ export function FileEditToolView({
<div className="p-1 py-0 prose dark:prose-invert prose-zinc max-w-none">
<MarkdownRenderer
content={processUnicodeContent(updatedContent)}
project={project}
/>
</div>
);

View File

@ -183,7 +183,7 @@ export function FileOperationToolView({
if (isHtml && htmlPreviewUrl) {
return (
<div className="flex flex-col h-[calc(100vh-16rem)]">
<div className="flex flex-col h-full min-h-[400px]">
<iframe
src={htmlPreviewUrl}
title={`HTML Preview of ${fileName}`}
@ -199,6 +199,7 @@ export function FileOperationToolView({
<div className="p-1 py-0 prose dark:prose-invert prose-zinc max-w-none">
<MarkdownRenderer
content={processUnicodeContent(fileContent)}
project={project}
/>
</div>
);
@ -206,8 +207,8 @@ export function FileOperationToolView({
if (isCsv) {
return (
<div className="h-full w-full p-4">
<div className="h-[calc(100vh-17rem)] w-full bg-muted/20 border rounded-xl overflow-auto">
<div className="h-full w-full p-4 flex flex-col">
<div className="flex-1 min-h-[400px] w-full bg-muted/20 border rounded-xl overflow-hidden">
<CsvRenderer content={processUnicodeContent(fileContent)} />
</div>
</div>
@ -216,8 +217,8 @@ export function FileOperationToolView({
if (isXlsx) {
return (
<div className="h-full w-full p-4">
<div className="h-[calc(100vh-17rem)] w-full bg-muted/20 border rounded-xl overflow-auto">
<div className="h-full w-full p-4 flex flex-col">
<div className="flex-1 min-h-[400px] w-full bg-muted/20 border rounded-xl overflow-hidden">
<XlsxRenderer
content={fileContent}
filePath={processedFilePath}
@ -379,7 +380,7 @@ export function FileOperationToolView({
<CardContent className="p-0 -my-2 h-full flex-1 overflow-hidden relative">
<TabsContent value="code" className="flex-1 h-full mt-0 p-0 overflow-hidden">
<ScrollArea className="h-screen w-full min-h-0">
<ScrollArea className="h-full w-full min-h-0">
{isStreaming && !fileContent ? (
<LoadingState
icon={Icon}

View File

@ -322,6 +322,7 @@ export function constructImageUrl(filePath: string, project?: { sandbox?: { sand
return cleanPath;
}
// PREFER backend API (requires authentication but more reliable)
const sandboxId = typeof project?.sandbox === 'string'
? project.sandbox
: project?.sandbox?.id;
@ -336,6 +337,7 @@ export function constructImageUrl(filePath: string, project?: { sandbox?: { sand
return apiEndpoint;
}
// Fallback to sandbox_url for direct access
if (project?.sandbox?.sandbox_url) {
const sandboxUrl = project.sandbox.sandbox_url.replace(/\/$/, '');
let normalizedPath = cleanPath;