mirror of https://github.com/kortix-ai/suna.git
md image render & fix tool call cut off
This commit is contained in:
parent
14edb083e7
commit
d782582d74
|
@ -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' ? (
|
||||
|
|
|
@ -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 }) => (
|
||||
|
|
|
@ -552,6 +552,7 @@ export function FileAttachment({
|
|||
content={fileContent}
|
||||
previewUrl={fileUrl}
|
||||
className="h-full w-full"
|
||||
project={project}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -8,6 +8,7 @@ import { cn } from '@/lib/utils';
|
|||
interface CsvRendererProps {
|
||||
content: string;
|
||||
className?: string;
|
||||
project?: any; // Optional to allow passing from file-attachment
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)) && (
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue