Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions src/features/dashboard/sandbox/inspect/filesystem/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,38 @@ import { FilesystemNode } from './types'

enableMapSet()

export const MAX_VIEWABLE_FILE_SIZE_BYTES = 15 * 1024 * 1024 // 15MB

interface FilesystemStatics {
rootPath: string
}

interface ContentFileContentState {
text: string
export interface TextFileContentState {
type: 'text'
text: string
}

export interface ImageFileContentState {
type: 'image'
dataUri: string
}

interface UnreadableFileContentState {
export interface UnreadableFileTypeContentState {
type: 'unreadable'
reason: 'file_type'
}

interface ImageFileContentState {
dataUri: string
type: 'image'
export interface UnreadableTooLargeContentState {
type: 'unreadable'
reason: 'too_large'
size: number
}

export type FileContentState =
| ContentFileContentState
| UnreadableFileContentState
| TextFileContentState
| ImageFileContentState
| UnreadableFileTypeContentState
| UnreadableTooLargeContentState

export type FileContentStateType = FileContentState['type']

Expand Down
26 changes: 23 additions & 3 deletions src/features/dashboard/sandbox/inspect/sandbox-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ import {
normalizePath,
} from '@/lib/utils/filesystem'
import {
FilesystemEventType,
type EntryInfo,
type FilesystemEvent,
FilesystemEventType,
type Sandbox,
type WatchHandle,
} from 'e2b'
import type { FilesystemStore } from './filesystem/store'
import {
MAX_VIEWABLE_FILE_SIZE_BYTES,
type FilesystemStore,
} from './filesystem/store'
import { FilesystemNode } from './filesystem/types'

export const HANDLED_ERRORS = {
Expand Down Expand Up @@ -322,6 +325,20 @@ export class SandboxManager {
try {
state.setLoading(normalizedPath, true)

const fileInfo = await this.sandbox.files.getInfo(normalizedPath, {
user: 'root',
requestTimeoutMs: 10_000,
})

if (fileInfo.size > MAX_VIEWABLE_FILE_SIZE_BYTES) {
state.setFileContent(normalizedPath, {
type: 'unreadable',
reason: 'too_large',
size: fileInfo.size,
})
return
}

const blob = await this.sandbox.files.read(normalizedPath, {
format: 'blob',
requestTimeoutMs: 30_000,
Expand All @@ -337,7 +354,10 @@ export class SandboxManager {
console.error(`Failed to read file ${normalizedPath}:`, err)

state.setError(normalizedPath, errorMessage)
state.setFileContent(normalizedPath, { type: 'unreadable' })
state.setFileContent(normalizedPath, {
type: 'unreadable',
reason: 'file_type',
})
} finally {
state.setLoading(normalizedPath, false)
state.setLoaded(normalizedPath, true)
Expand Down
34 changes: 29 additions & 5 deletions src/features/dashboard/sandbox/inspect/viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ import { AnimatePresence } from 'framer-motion'
import { Download } from 'lucide-react'
import { useEffect, useState } from 'react'
import ShikiHighlighter, { Language } from 'react-shiki'

import {
MAX_VIEWABLE_FILE_SIZE_BYTES,
type UnreadableFileTypeContentState,
type UnreadableTooLargeContentState,
} from './filesystem/store'
import SandboxInspectFrame from './frame'
import { useContent } from './hooks/use-content'
import { useFile } from './hooks/use-file'
Expand Down Expand Up @@ -117,7 +123,7 @@ function SandboxInspectViewerContent({
) : state.type === 'image' ? (
<ImageContent name={name} dataUri={state.dataUri} />
) : (
<UnreadableContent onDownload={download} />
<UnreadableContent state={state} onDownload={download} />
)}
</SandboxInspectFrame>
)
Expand Down Expand Up @@ -148,7 +154,7 @@ function TextContent({
if (content.length === 0) {
return (
<div className="flex h-full flex-col items-center justify-center gap-3">
<span className="text-fg-secondary ">This file is empty.</span>
<span className="text-fg-secondary">This file is empty.</span>
<Button variant="warning" size="sm" onClick={onDownload}>
Download
<Download className="ml-1.5 h-4 w-4" />
Expand Down Expand Up @@ -195,14 +201,32 @@ function ImageContent({ name, dataUri }: ImageContentProps) {
)
}

interface UnreadableContent {
interface UnreadableContentProps {
state: UnreadableFileTypeContentState | UnreadableTooLargeContentState
onDownload: () => void
}

function UnreadableContent({ onDownload }: UnreadableContent) {
function UnreadableContent({ state, onDownload }: UnreadableContentProps) {
const maxSizeMB = (MAX_VIEWABLE_FILE_SIZE_BYTES / 1024 / 1024).toFixed(0)

if (state.reason === 'too_large') {
const sizeMB = (state.size / 1024 / 1024).toFixed(1)
return (
<div className="flex h-full flex-col items-center justify-center gap-3">
<span className="text-fg-secondary">
File is too large to preview ({sizeMB} MB). Limit is {maxSizeMB} MB.
</span>
<Button variant="warning" size="sm" onClick={onDownload}>
Download
<Download className="ml-1.5 h-4 w-4" />
</Button>
</div>
)
}

return (
<div className="flex h-full flex-col items-center justify-center gap-3">
<span className="text-fg-secondary ">This file is not readable.</span>
<span className="text-fg-secondary">This file is not readable.</span>
<Button variant="warning" size="sm" onClick={onDownload}>
Download
<Download className="ml-1.5 h-4 w-4" />
Expand Down
2 changes: 1 addition & 1 deletion src/lib/utils/filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,6 @@ export async function determineFileContentState(
const content = new TextDecoder('utf-8', { fatal: true }).decode(data)
return { type: 'text', text: content }
} catch {
return { type: 'unreadable' }
return { type: 'unreadable', reason: 'file_type' }
}
}
Loading