FileViewerModal component in Suna, an open source generalist AI agent.
In this article, we review a component named FileViewerModal in Suna codebase. Suna is an Open Source Generalist AI Agent. Where is FileViewerModal component used? I explored Suna by creating an account and created few chats. As part of the conversation response, there are tags and list of files responded as shown in the below image. When you click on any file here, the below modal appears. This is rendered by the FileViewerModal component. If you look at this page’s url, it looks something like this — https://www.suna.so/agents/{id} Suna does not use any monorepo setup, it contains backend and frontend folders. Frontend is built using Next.js app router, so we can expect a folder named “agents” and a dynamic route. As you can see, the [threadId] folder is used for dynamic routing in app router based Next.js project. At line 1548 in [threadId]/page.tsx, you will find the below code: {sandboxId && ( )} FileViewerModal component This component is defined in components/thread/file-viewer-modal.tsx. At the time of writing this article, this FileViewerModal component has 905 LOC. Below code is response to fetch the file that should be loaded in the viewer. // Load files when modal opens or path changes - Refined useEffect(() => { if (!open || !sandboxId) { return; // Don't load if modal is closed or no sandbox ID } const loadFiles = async () => { setIsLoadingFiles(true); console.log(`[FILE VIEWER] useEffect[currentPath]: Triggered. Loading files for path: ${currentPath}`); try { const filesData = await listSandboxFiles(sandboxId, currentPath); console.log(`[FILE VIEWER] useEffect[currentPath]: API returned ${filesData.length} files.`); setFiles(filesData); } catch (error) { console.error("Failed to load files:", error); toast.error("Failed to load files"); setFiles([]); } finally { setIsLoadingFiles(false); } }; loadFiles(); // Dependency: Only re-run when open, sandboxId, or currentPath changes }, [open, sandboxId, currentPath]); Dialog component used is a Shadcn/ui component, located at components/ui/dialog.tsx. File is rendered using another component called FileRenderer. About me: Hey, my name is Ramu Narasinga. I study large open-source projects and create content about their codebase architecture and best practices, sharing it through articles, videos. Want me to review your codebase architecture? book a meeting — https://thinkthroo.com/consultation/codebase-architecture-review Business enquiries — ramu@thinkthroo.com My Github — https://github.com/ramu-narasinga My website — https://ramunarasinga.com My YouTube channel — https://www.youtube.com/@ramu-narasinga Learning platform — https://thinkthroo.com Codebase Architecture — https://app.thinkthroo.com/architecture Best practices — https://app.thinkthroo.com/best-practices Production-grade projects — https://app.thinkthroo.com/production-grade-projects References: https://github.com/kortix-ai/suna/blob/main/frontend/src/app/(dashboard)/agents/%5BthreadId%5D/page.tsx#L1610 https://github.com/kortix-ai/suna/blob/main/frontend/src/components/thread/file-viewer-modal.tsx#L39

In this article, we review a component named FileViewerModal in Suna codebase. Suna is an Open Source Generalist AI Agent.
Where is FileViewerModal component used?
I explored Suna by creating an account and created few chats. As part of the conversation response, there are tags and list of files responded as shown in the below image.
When you click on any file here, the below modal appears.
This is rendered by the FileViewerModal component. If you look at this page’s url, it looks something like this — https://www.suna.so/agents/{id}
Suna does not use any monorepo setup, it contains backend and frontend folders. Frontend is built using Next.js app router, so we can expect a folder named “agents” and a dynamic route.
As you can see, the [threadId]
folder is used for dynamic routing in app router based Next.js project. At line 1548 in [threadId]/page.tsx, you will find the below code:
{sandboxId && (
<FileViewerModal
open={fileViewerOpen}
onOpenChange={setFileViewerOpen}
sandboxId={sandboxId}
initialFilePath={fileToView}
project={project || undefined}
/>
)}
FileViewerModal component
This component is defined in components/thread/file-viewer-modal.tsx. At the time of writing this article, this FileViewerModal component has 905 LOC.
Below code is response to fetch the file that should be loaded in the viewer.
// Load files when modal opens or path changes - Refined
useEffect(() => {
if (!open || !sandboxId) {
return; // Don't load if modal is closed or no sandbox ID
}
const loadFiles = async () => {
setIsLoadingFiles(true);
console.log(`[FILE VIEWER] useEffect[currentPath]: Triggered. Loading files for path: ${currentPath}`);
try {
const filesData = await listSandboxFiles(sandboxId, currentPath);
console.log(`[FILE VIEWER] useEffect[currentPath]: API returned ${filesData.length} files.`);
setFiles(filesData);
} catch (error) {
console.error("Failed to load files:", error);
toast.error("Failed to load files");
setFiles([]);
} finally {
setIsLoadingFiles(false);
}
};
loadFiles();
// Dependency: Only re-run when open, sandboxId, or currentPath changes
}, [open, sandboxId, currentPath]);
Dialog component used is a Shadcn/ui component, located at components/ui/dialog.tsx.
File is rendered using another component called FileRenderer.
<div className="h-full w-full relative">
<FileRenderer
key={selectedFilePath}
content={textContentForRenderer}
binaryUrl={blobUrlForRenderer}
fileName={selectedFilePath}
className="h-full w-full"
project={projectWithSandbox}
markdownRef={isMarkdownFile(selectedFilePath) ? markdownRef : undefined}
/>
</div>
About me:
Hey, my name is Ramu Narasinga. I study large open-source projects and create content about their codebase architecture and best practices, sharing it through articles, videos.
Want me to review your codebase architecture? book a meeting — https://thinkthroo.com/consultation/codebase-architecture-review
Business enquiries — ramu@thinkthroo.com
My Github — https://github.com/ramu-narasinga
My website — https://ramunarasinga.com
My YouTube channel — https://www.youtube.com/@ramu-narasinga
Learning platform — https://thinkthroo.com
Codebase Architecture — https://app.thinkthroo.com/architecture
Best practices — https://app.thinkthroo.com/best-practices
Production-grade projects — https://app.thinkthroo.com/production-grade-projects