diff --git a/.changeset/gentle-knives-unite.md b/.changeset/gentle-knives-unite.md
new file mode 100644
index 0000000000000000000000000000000000000000..b8b8475cec399f8f63acfbbfe55392e4213cebd0
--- /dev/null
+++ b/.changeset/gentle-knives-unite.md
@@ -0,0 +1,5 @@
+---
+"create-llama": patch
+---
+
+Add interpreter tool for TS using e2b.dev
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index 8ea8530fb08df7f21165c3848fdd35f7066d82a1..15df5c8bb6d9b1e9788d7b9e277f4ec6a0a863b3 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -26,7 +26,7 @@ jobs:
       - uses: actions/checkout@v4
 
       - name: Set up python ${{ matrix.python-version }}
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@v5
         with:
           python-version: ${{ matrix.python-version }}
 
diff --git a/helpers/env-variables.ts b/helpers/env-variables.ts
index 34fdc952aaf73132461516733859a35a3da1d14b..7f315fe7dcf96e24aec96c13e82e02abc2c3b5e7 100644
--- a/helpers/env-variables.ts
+++ b/helpers/env-variables.ts
@@ -1,5 +1,6 @@
 import fs from "fs/promises";
 import path from "path";
+import { TOOL_SYSTEM_PROMPT_ENV_VAR, Tool } from "./tools";
 import {
   ModelConfig,
   TemplateDataSource,
@@ -7,7 +8,7 @@ import {
   TemplateVectorDB,
 } from "./types";
 
-type EnvVar = {
+export type EnvVar = {
   name?: string;
   description?: string;
   value?: string;
@@ -232,24 +233,39 @@ const getModelEnvs = (modelConfig: ModelConfig): EnvVar[] => {
 };
 
 const getFrameworkEnvs = (
-  framework?: TemplateFramework,
+  framework: TemplateFramework,
   port?: number,
 ): EnvVar[] => {
-  if (framework !== "fastapi") {
-    return [];
-  }
-  return [
-    {
-      name: "APP_HOST",
-      description: "The address to start the backend app.",
-      value: "0.0.0.0",
-    },
+  const sPort = port?.toString() || "8000";
+  const result: EnvVar[] = [
     {
-      name: "APP_PORT",
-      description: "The port to start the backend app.",
-      value: port?.toString() || "8000",
+      name: "FILESERVER_URL_PREFIX",
+      description:
+        "FILESERVER_URL_PREFIX is the URL prefix of the server storing the images generated by the interpreter.",
+      value:
+        framework === "nextjs"
+          ? // FIXME: if we are using nextjs, port should be 3000
+            "http://localhost:3000/api/files"
+          : `http://localhost:${sPort}/api/files`,
     },
   ];
+  if (framework === "fastapi") {
+    result.push(
+      ...[
+        {
+          name: "APP_HOST",
+          description: "The address to start the backend app.",
+          value: "0.0.0.0",
+        },
+        {
+          name: "APP_PORT",
+          description: "The port to start the backend app.",
+          value: sPort,
+        },
+      ],
+    );
+  }
+  return result;
 };
 
 const getEngineEnvs = (): EnvVar[] => {
@@ -260,24 +276,62 @@ const getEngineEnvs = (): EnvVar[] => {
         "The number of similar embeddings to return when retrieving documents.",
       value: "3",
     },
-    {
-      name: "SYSTEM_PROMPT",
-      description: `Custom system prompt.
-Example:
-SYSTEM_PROMPT="You are a helpful assistant who helps users with their questions."`,
-    },
   ];
 };
 
+const getToolEnvs = (tools?: Tool[]): EnvVar[] => {
+  if (!tools?.length) return [];
+  const toolEnvs: EnvVar[] = [];
+  tools.forEach((tool) => {
+    if (tool.envVars?.length) {
+      toolEnvs.push(
+        // Don't include the system prompt env var here
+        // It should be handled separately by merging with the default system prompt
+        ...tool.envVars.filter(
+          (env) => env.name !== TOOL_SYSTEM_PROMPT_ENV_VAR,
+        ),
+      );
+    }
+  });
+  return toolEnvs;
+};
+
+const getSystemPromptEnv = (tools?: Tool[]): EnvVar => {
+  const defaultSystemPrompt =
+    "You are a helpful assistant who helps users with their questions.";
+
+  // build tool system prompt by merging all tool system prompts
+  let toolSystemPrompt = "";
+  tools?.forEach((tool) => {
+    const toolSystemPromptEnv = tool.envVars?.find(
+      (env) => env.name === TOOL_SYSTEM_PROMPT_ENV_VAR,
+    );
+    if (toolSystemPromptEnv) {
+      toolSystemPrompt += toolSystemPromptEnv.value + "\n";
+    }
+  });
+
+  const systemPrompt = toolSystemPrompt
+    ? `\"${toolSystemPrompt}\"`
+    : defaultSystemPrompt;
+
+  return {
+    name: "SYSTEM_PROMPT",
+    description: "The system prompt for the AI model.",
+    value: systemPrompt,
+  };
+};
+
 export const createBackendEnvFile = async (
   root: string,
   opts: {
     llamaCloudKey?: string;
     vectorDb?: TemplateVectorDB;
     modelConfig: ModelConfig;
-    framework?: TemplateFramework;
+    framework: TemplateFramework;
     dataSources?: TemplateDataSource[];
     port?: number;
+    tools?: Tool[];
   },
 ) => {
   // Init env values
@@ -295,6 +349,8 @@ export const createBackendEnvFile = async (
     // Add vector database environment variables
     ...getVectorDBEnvs(opts.vectorDb, opts.framework),
     ...getFrameworkEnvs(opts.framework, opts.port),
+    ...getToolEnvs(opts.tools),
+    getSystemPromptEnv(opts.tools),
   ];
   // Render and write env file
   const content = renderEnvVar(envVars);
diff --git a/helpers/index.ts b/helpers/index.ts
index 571182f186a73396faccf1dc59dbcb1cffa730cb..ef602d28b35236a51625512255b88c59444e148d 100644
--- a/helpers/index.ts
+++ b/helpers/index.ts
@@ -148,6 +148,7 @@ export const installTemplate = async (
       framework: props.framework,
       dataSources: props.dataSources,
       port: props.externalPort,
+      tools: props.tools,
     });
 
     if (props.dataSources.length > 0) {
diff --git a/helpers/tools.ts b/helpers/tools.ts
index 95890d3982f0f7af1784bcf032b21b159dd59c02..a881ef1498a45d782f50f11682ae6fddca14a49b 100644
--- a/helpers/tools.ts
+++ b/helpers/tools.ts
@@ -2,9 +2,12 @@ import fs from "fs/promises";
 import path from "path";
 import { red } from "picocolors";
 import yaml from "yaml";
+import { EnvVar } from "./env-variables";
 import { makeDir } from "./make-dir";
 import { TemplateFramework } from "./types";
 
+export const TOOL_SYSTEM_PROMPT_ENV_VAR = "TOOL_SYSTEM_PROMPT";
+
 export enum ToolType {
   LLAMAHUB = "llamahub",
   LOCAL = "local",
@@ -17,6 +20,7 @@ export type Tool = {
   dependencies?: ToolDependencies[];
   supportedFrameworks?: Array<TemplateFramework>;
   type: ToolType;
+  envVars?: EnvVar[];
 };
 
 export type ToolDependencies = {
@@ -42,6 +46,13 @@ export const supportedTools: Tool[] = [
     ],
     supportedFrameworks: ["fastapi"],
     type: ToolType.LLAMAHUB,
+    envVars: [
+      {
+        name: TOOL_SYSTEM_PROMPT_ENV_VAR,
+        description: "System prompt for google search tool.",
+        value: `You are a Google search agent. You help users to get information from Google search.`,
+      },
+    ],
   },
   {
     display: "Wikipedia",
@@ -54,6 +65,13 @@ export const supportedTools: Tool[] = [
     ],
     supportedFrameworks: ["fastapi", "express", "nextjs"],
     type: ToolType.LLAMAHUB,
+    envVars: [
+      {
+        name: TOOL_SYSTEM_PROMPT_ENV_VAR,
+        description: "System prompt for wiki tool.",
+        value: `You are a Wikipedia agent. You help users to get information from Wikipedia.`,
+      },
+    ],
   },
   {
     display: "Weather",
@@ -61,6 +79,38 @@ export const supportedTools: Tool[] = [
     dependencies: [],
     supportedFrameworks: ["fastapi", "express", "nextjs"],
     type: ToolType.LOCAL,
+    envVars: [
+      {
+        name: TOOL_SYSTEM_PROMPT_ENV_VAR,
+        description: "System prompt for weather tool.",
+        value: `You are a weather forecast agent. You help users to get the weather forecast for a given location.`,
+      },
+    ],
+  },
+  {
+    display: "Code Interpreter",
+    name: "interpreter",
+    dependencies: [],
+    supportedFrameworks: ["express", "nextjs"],
+    type: ToolType.LOCAL,
+    envVars: [
+      {
+        name: "E2B_API_KEY",
+        description:
+          "E2B_API_KEY key is required to run code interpreter tool. Get it here: https://e2b.dev/docs/getting-started/api-key",
+      },
+      {
+        name: TOOL_SYSTEM_PROMPT_ENV_VAR,
+        description: "System prompt for code interpreter tool.",
+        value: `You are a Python interpreter.
+        - You are given tasks to complete and you run python code to solve them.
+        - The python code runs in a Jupyter notebook. Every time you call \`interpreter\` tool, the python code is executed in a separate cell. It's okay to make multiple calls to \`interpreter\`.
+        - Display visualizations using matplotlib or any other visualization library directly in the notebook. Shouldn't save the visualizations to a file, just return the base64 encoded data.
+        - You can install any pip package (if it exists) if you need to but the usual packages for data analysis are already preinstalled.
+        - You can run any python code you want in a secure environment.
+        - Use absolute url from result to display images or any other media.`,
+      },
+    ],
   },
 ];
 
diff --git a/templates/components/engines/typescript/agent/chat.ts b/templates/components/engines/typescript/agent/chat.ts
index 856d36ee5029c0d808fcdb47c470ef1c798b9828..0c9c920fbe948ca43ac37c03dd793b4e933e06bc 100644
--- a/templates/components/engines/typescript/agent/chat.ts
+++ b/templates/components/engines/typescript/agent/chat.ts
@@ -1,10 +1,9 @@
 import { BaseToolWithCall, OpenAIAgent, QueryEngineTool } from "llamaindex";
-import { ToolsFactory } from "llamaindex/tools/ToolsFactory";
 import fs from "node:fs/promises";
 import path from "node:path";
 import { getDataSource } from "./index";
 import { STORAGE_CACHE_DIR } from "./shared";
-import { createLocalTools } from "./tools";
+import { createTools } from "./tools";
 
 export async function createChatEngine() {
   const tools: BaseToolWithCall[] = [];
@@ -24,20 +23,17 @@ export async function createChatEngine() {
     );
   }
 
+  const configFile = path.join("config", "tools.json");
+  let toolConfig: any;
   try {
     // add tools from config file if it exists
-    const config = JSON.parse(
-      await fs.readFile(path.join("config", "tools.json"), "utf8"),
-    );
-
-    // add local tools from the 'tools' folder (if configured)
-    const localTools = createLocalTools(config.local);
-    tools.push(...localTools);
-
-    // add tools from LlamaIndexTS (if configured)
-    const llamaTools = await ToolsFactory.createTools(config.llamahub);
-    tools.push(...llamaTools);
-  } catch {}
+    toolConfig = JSON.parse(await fs.readFile(configFile, "utf8"));
+  } catch (e) {
+    console.info(`Could not read ${configFile} file. Using no tools.`);
+  }
+  if (toolConfig) {
+    tools.push(...(await createTools(toolConfig)));
+  }
 
   return new OpenAIAgent({
     tools,
diff --git a/templates/components/engines/typescript/agent/tools/index.ts b/templates/components/engines/typescript/agent/tools/index.ts
index 8d041a766f204317d84cffa12b0d5aa596722169..faff9b5dd2518904f0b9b01916fb71bf277878f7 100644
--- a/templates/components/engines/typescript/agent/tools/index.ts
+++ b/templates/components/engines/typescript/agent/tools/index.ts
@@ -1,15 +1,31 @@
 import { BaseToolWithCall } from "llamaindex";
+import { ToolsFactory } from "llamaindex/tools/ToolsFactory";
+import { InterpreterTool, InterpreterToolParams } from "./interpreter";
 import { WeatherTool, WeatherToolParams } from "./weather";
 
 type ToolCreator = (config: unknown) => BaseToolWithCall;
 
+export async function createTools(toolConfig: {
+  local: Record<string, unknown>;
+  llamahub: any;
+}): Promise<BaseToolWithCall[]> {
+  // add local tools from the 'tools' folder (if configured)
+  const tools = createLocalTools(toolConfig.local);
+  // add tools from LlamaIndexTS (if configured)
+  tools.push(...(await ToolsFactory.createTools(toolConfig.llamahub)));
+  return tools;
+}
+
 const toolFactory: Record<string, ToolCreator> = {
   weather: (config: unknown) => {
     return new WeatherTool(config as WeatherToolParams);
   },
+  interpreter: (config: unknown) => {
+    return new InterpreterTool(config as InterpreterToolParams);
+  },
 };
 
-export function createLocalTools(
+function createLocalTools(
   localConfig: Record<string, unknown>,
 ): BaseToolWithCall[] {
   const tools: BaseToolWithCall[] = [];
diff --git a/templates/components/engines/typescript/agent/tools/interpreter.ts b/templates/components/engines/typescript/agent/tools/interpreter.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d34aa6c167bca6a3a677ca3f57a06fac50de9150
--- /dev/null
+++ b/templates/components/engines/typescript/agent/tools/interpreter.ts
@@ -0,0 +1,174 @@
+import { CodeInterpreter, Logs, Result } from "@e2b/code-interpreter";
+import type { JSONSchemaType } from "ajv";
+import fs from "fs";
+import { BaseTool, ToolMetadata } from "llamaindex";
+import crypto from "node:crypto";
+import path from "node:path";
+
+export type InterpreterParameter = {
+  code: string;
+};
+
+export type InterpreterToolParams = {
+  metadata?: ToolMetadata<JSONSchemaType<InterpreterParameter>>;
+  apiKey?: string;
+  fileServerURLPrefix?: string;
+};
+
+export type InterpreterToolOuput = {
+  isError: boolean;
+  logs: Logs;
+  extraResult: InterpreterExtraResult[];
+};
+
+type InterpreterExtraType =
+  | "html"
+  | "markdown"
+  | "svg"
+  | "png"
+  | "jpeg"
+  | "pdf"
+  | "latex"
+  | "json"
+  | "javascript";
+
+export type InterpreterExtraResult = {
+  type: InterpreterExtraType;
+  filename: string;
+  url: string;
+};
+
+const DEFAULT_META_DATA: ToolMetadata<JSONSchemaType<InterpreterParameter>> = {
+  name: "interpreter",
+  description:
+    "Execute python code in a Jupyter notebook cell and return any result, stdout, stderr, display_data, and error.",
+  parameters: {
+    type: "object",
+    properties: {
+      code: {
+        type: "string",
+        description: "The python code to execute in a single cell.",
+      },
+    },
+    required: ["code"],
+  },
+};
+
+export class InterpreterTool implements BaseTool<InterpreterParameter> {
+  private readonly outputDir = "tool-output";
+  private apiKey?: string;
+  private fileServerURLPrefix?: string;
+  metadata: ToolMetadata<JSONSchemaType<InterpreterParameter>>;
+  codeInterpreter?: CodeInterpreter;
+
+  constructor(params?: InterpreterToolParams) {
+    this.metadata = params?.metadata || DEFAULT_META_DATA;
+    this.apiKey = params?.apiKey || process.env.E2B_API_KEY;
+    this.fileServerURLPrefix =
+      params?.fileServerURLPrefix || process.env.FILESERVER_URL_PREFIX;
+
+    if (!this.apiKey) {
+      throw new Error(
+        "E2B_API_KEY key is required to run code interpreter. Get it here: https://e2b.dev/docs/getting-started/api-key",
+      );
+    }
+    if (!this.fileServerURLPrefix) {
+      throw new Error(
+        "FILESERVER_URL_PREFIX is required to display file output from sandbox",
+      );
+    }
+  }
+
+  public async initInterpreter() {
+    if (!this.codeInterpreter) {
+      this.codeInterpreter = await CodeInterpreter.create({
+        apiKey: this.apiKey,
+      });
+    }
+    return this.codeInterpreter;
+  }
+
+  public async codeInterpret(code: string): Promise<InterpreterToolOuput> {
+    console.log(
+      `\n${"=".repeat(50)}\n> Running following AI-generated code:\n${code}\n${"=".repeat(50)}`,
+    );
+    const interpreter = await this.initInterpreter();
+    const exec = await interpreter.notebook.execCell(code);
+    if (exec.error) console.error("[Code Interpreter error]", exec.error);
+    const extraResult = await this.getExtraResult(exec.results[0]);
+    const result: InterpreterToolOuput = {
+      isError: !!exec.error,
+      logs: exec.logs,
+      extraResult,
+    };
+    return result;
+  }
+
+  async call(input: InterpreterParameter): Promise<InterpreterToolOuput> {
+    const result = await this.codeInterpret(input.code);
+    await this.codeInterpreter?.close();
+    return result;
+  }
+
+  private async getExtraResult(
+    res?: Result,
+  ): Promise<InterpreterExtraResult[]> {
+    if (!res) return [];
+    const output: InterpreterExtraResult[] = [];
+
+    try {
+      const formats = res.formats(); // formats available for the result. Eg: ['png', ...]
+      const base64DataArr = formats.map((f) => res[f as keyof Result]); // get base64 data for each format
+
+      // save base64 data to file and return the url
+      for (let i = 0; i < formats.length; i++) {
+        const ext = formats[i];
+        const base64Data = base64DataArr[i];
+        if (ext && base64Data) {
+          const { filename } = this.saveToDisk(base64Data, ext);
+          output.push({
+            type: ext as InterpreterExtraType,
+            filename,
+            url: this.getFileUrl(filename),
+          });
+        }
+      }
+    } catch (error) {
+      console.error("Error when saving data to disk", error);
+    }
+
+    return output;
+  }
+
+  // Consider saving to cloud storage instead but it may cost more for you
+  // See: https://e2b.dev/docs/sandbox/api/filesystem#write-to-file
+  private saveToDisk(
+    base64Data: string,
+    ext: string,
+  ): {
+    outputPath: string;
+    filename: string;
+  } {
+    const filename = `${crypto.randomUUID()}.${ext}`; // generate a unique filename
+    const buffer = Buffer.from(base64Data, "base64");
+    const outputPath = this.getOutputPath(filename);
+    fs.writeFileSync(outputPath, buffer);
+    console.log(`Saved file to ${outputPath}`);
+    return {
+      outputPath,
+      filename,
+    };
+  }
+
+  private getOutputPath(filename: string): string {
+    // if outputDir doesn't exist, create it
+    if (!fs.existsSync(this.outputDir)) {
+      fs.mkdirSync(this.outputDir, { recursive: true });
+    }
+    return path.join(this.outputDir, filename);
+  }
+
+  private getFileUrl(filename: string): string {
+    return `${this.fileServerURLPrefix}/${this.outputDir}/${filename}`;
+  }
+}
diff --git a/templates/types/streaming/express/gitignore b/templates/types/streaming/express/gitignore
index 7d5e30fc24e77107da0e35176a5c3caebe7f8862..02c8b1de592d5aee25b23357ebb27b45e534272b 100644
--- a/templates/types/streaming/express/gitignore
+++ b/templates/types/streaming/express/gitignore
@@ -1,3 +1,5 @@
 # local env files
 .env
-node_modules/
\ No newline at end of file
+node_modules/
+
+tool-output/
\ No newline at end of file
diff --git a/templates/types/streaming/express/index.ts b/templates/types/streaming/express/index.ts
index 5940c09d0b7531cbda98e6e77af7d0a74ba5c4fe..a821739f0651cfbb5bc91f978d4822a67e1be905 100644
--- a/templates/types/streaming/express/index.ts
+++ b/templates/types/streaming/express/index.ts
@@ -31,7 +31,8 @@ if (isDevelopment) {
   console.warn("Production CORS origin not set, defaulting to no CORS.");
 }
 
-app.use("/api/data", express.static("data"));
+app.use("/api/files/data", express.static("data"));
+app.use("/api/files/tool-output", express.static("tool-output"));
 app.use(express.text());
 
 app.get("/", (req: Request, res: Response) => {
diff --git a/templates/types/streaming/express/package.json b/templates/types/streaming/express/package.json
index b8bb9a71dc95e7215ba2f4253a39f0155b624334..449094354e4598491660ec3b957a80e5303fd2db 100644
--- a/templates/types/streaming/express/package.json
+++ b/templates/types/streaming/express/package.json
@@ -16,7 +16,8 @@
     "express": "^4.18.2",
     "llamaindex": "0.3.13",
     "pdf2json": "3.0.5",
-    "ajv": "^8.12.0"
+    "ajv": "^8.12.0",
+    "@e2b/code-interpreter": "^0.0.5"
   },
   "devDependencies": {
     "@types/cors": "^2.8.16",
diff --git a/templates/types/streaming/fastapi/gitignore b/templates/types/streaming/fastapi/gitignore
index a6ad564cd45eee8c8bd1fdc633d733d17b4a777a..0fb5f02296dfd903ed5088a0e93a90d567e299ff 100644
--- a/templates/types/streaming/fastapi/gitignore
+++ b/templates/types/streaming/fastapi/gitignore
@@ -1,3 +1,3 @@
 __pycache__
 storage
-.env
+.env
\ No newline at end of file
diff --git a/templates/types/streaming/fastapi/main.py b/templates/types/streaming/fastapi/main.py
index c053fd6d28f9b12718f9e5562c3e5880ffb9ba98..77d3032178c5888097e78c6a81d12f67c2eae107 100644
--- a/templates/types/streaming/fastapi/main.py
+++ b/templates/types/streaming/fastapi/main.py
@@ -37,9 +37,8 @@ if environment == "dev":
     async def redirect_to_docs():
         return RedirectResponse(url="/docs")
 
-
 if os.path.exists("data"):
-    app.mount("/api/data", StaticFiles(directory="data"), name="static")
+    app.mount("/api/files/data", StaticFiles(directory="data"), name="data-static")
 app.include_router(chat_router, prefix="/api/chat")
 
 
diff --git a/templates/types/streaming/nextjs/app/api/data/[path]/route.ts b/templates/types/streaming/nextjs/app/api/files/[...slug]/route.ts
similarity index 60%
rename from templates/types/streaming/nextjs/app/api/data/[path]/route.ts
rename to templates/types/streaming/nextjs/app/api/files/[...slug]/route.ts
index 8e5fb9271145501634fabd25aeb788ad487d022c..03c285e9bbe7ff9f8b8adef4eeb6fc5839db548f 100644
--- a/templates/types/streaming/nextjs/app/api/data/[path]/route.ts
+++ b/templates/types/streaming/nextjs/app/api/files/[...slug]/route.ts
@@ -3,25 +3,32 @@ import { NextRequest, NextResponse } from "next/server";
 import path from "path";
 
 /**
- * This API is to get file data from ./data folder
+ * This API is to get file data from allowed folders
  * It receives path slug and response file data like serve static file
  */
 export async function GET(
   _request: NextRequest,
-  { params }: { params: { path: string } },
+  { params }: { params: { slug: string[] } },
 ) {
-  const slug = params.path;
+  const slug = params.slug;
 
   if (!slug) {
     return NextResponse.json({ detail: "Missing file slug" }, { status: 400 });
   }
 
-  if (slug.includes("..") || path.isAbsolute(slug)) {
+  if (slug.includes("..") || path.isAbsolute(path.join(...slug))) {
     return NextResponse.json({ detail: "Invalid file path" }, { status: 400 });
   }
 
+  const [folder, ...pathTofile] = params.slug; // data, file.pdf
+  const allowedFolders = ["data", "tool-output"];
+
+  if (!allowedFolders.includes(folder)) {
+    return NextResponse.json({ detail: "No permission" }, { status: 400 });
+  }
+
   try {
-    const filePath = path.join(process.cwd(), "data", slug);
+    const filePath = path.join(process.cwd(), folder, path.join(...pathTofile));
     const blob = await readFile(filePath);
 
     return new NextResponse(blob, {
diff --git a/templates/types/streaming/nextjs/app/components/ui/chat/chat-events.tsx b/templates/types/streaming/nextjs/app/components/ui/chat/chat-events.tsx
index eb921a981cb6fa013cb26387d44071e569cd2dad..af306768bd79f40e1d6dfd93433927a0cf54755b 100644
--- a/templates/types/streaming/nextjs/app/components/ui/chat/chat-events.tsx
+++ b/templates/types/streaming/nextjs/app/components/ui/chat/chat-events.tsx
@@ -38,7 +38,9 @@ export function ChatEvents({
         <CollapsibleContent asChild>
           <div className="mt-4 text-sm space-y-2">
             {data.map((eventItem, index) => (
-              <div key={index}>{eventItem.title}</div>
+              <div className="whitespace-break-spaces" key={index}>
+                {eventItem.title}
+              </div>
             ))}
           </div>
         </CollapsibleContent>
diff --git a/templates/types/streaming/nextjs/app/components/ui/chat/chat-sources.tsx b/templates/types/streaming/nextjs/app/components/ui/chat/chat-sources.tsx
index a492eebc50ec094a961e8be949db73e807ca404e..893541b09bb5cd0cf1f07d9a5bd0d4341a293f52 100644
--- a/templates/types/streaming/nextjs/app/components/ui/chat/chat-sources.tsx
+++ b/templates/types/streaming/nextjs/app/components/ui/chat/chat-sources.tsx
@@ -7,6 +7,7 @@ import { SourceData, SourceNode } from "./index";
 import { useCopyToClipboard } from "./use-copy-to-clipboard";
 import PdfDialog from "./widgets/PdfDialog";
 
+const DATA_SOURCE_FOLDER = "data";
 const SCORE_THRESHOLD = 0.3;
 
 function SourceNumberButton({ index }: { index: number }) {
@@ -42,11 +43,12 @@ function getNodeInfo(node: SourceNode): NodeInfo {
   }
   if (typeof node.metadata["file_path"] === "string") {
     const fileName = node.metadata["file_name"] as string;
+    const filePath = `${DATA_SOURCE_FOLDER}/${fileName}`;
     return {
       id: node.id,
       type: NODE_TYPE.FILE,
       path: node.metadata["file_path"],
-      url: getStaticFileDataUrl(fileName),
+      url: getStaticFileDataUrl(filePath),
     };
   }
 
diff --git a/templates/types/streaming/nextjs/app/components/ui/lib/url.ts b/templates/types/streaming/nextjs/app/components/ui/lib/url.ts
index 90236246c83e3f9b5ff18d9e3af98d263de94d4a..5e2c90e598045cbbfad2dc7ae5624b53ebfe9f92 100644
--- a/templates/types/streaming/nextjs/app/components/ui/lib/url.ts
+++ b/templates/types/streaming/nextjs/app/components/ui/lib/url.ts
@@ -1,11 +1,11 @@
-const STORAGE_FOLDER = "data";
+const staticFileAPI = "/api/files";
 
-export const getStaticFileDataUrl = (filename: string) => {
+export const getStaticFileDataUrl = (filePath: string) => {
   const isUsingBackend = !!process.env.NEXT_PUBLIC_CHAT_API;
-  const fileUrl = `/api/${STORAGE_FOLDER}/${filename}`;
+  const fileUrl = `${staticFileAPI}/${filePath}`;
   if (isUsingBackend) {
     const backendOrigin = new URL(process.env.NEXT_PUBLIC_CHAT_API!).origin;
-    return `${backendOrigin}/${fileUrl}`;
+    return `${backendOrigin}${fileUrl}`;
   }
   return fileUrl;
 };
diff --git a/templates/types/streaming/nextjs/gitignore b/templates/types/streaming/nextjs/gitignore
index 8f322f0d8f49570a594b865ef8916c428a01afc1..03d5c89b0dbd2aa3b96403c970c5d164a2f6bae9 100644
--- a/templates/types/streaming/nextjs/gitignore
+++ b/templates/types/streaming/nextjs/gitignore
@@ -33,3 +33,5 @@ yarn-error.log*
 # typescript
 *.tsbuildinfo
 next-env.d.ts
+
+tool-output/
\ No newline at end of file
diff --git a/templates/types/streaming/nextjs/package.json b/templates/types/streaming/nextjs/package.json
index ad182f7309b24dd95ade6bb23156af32ee1946b4..28f3d0665f66e23e74605062e7870a0081840b09 100644
--- a/templates/types/streaming/nextjs/package.json
+++ b/templates/types/streaming/nextjs/package.json
@@ -34,7 +34,8 @@
     "supports-color": "^8.1.1",
     "tailwind-merge": "^2.1.0",
     "vaul": "^0.9.1",
-    "@llamaindex/pdf-viewer": "^1.1.1"
+    "@llamaindex/pdf-viewer": "^1.1.1",
+    "@e2b/code-interpreter": "^0.0.5"
   },
   "devDependencies": {
     "@types/node": "^20.10.3",