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",