From ba1cb996cf4b39c037db1064fb3b29852b884670 Mon Sep 17 00:00:00 2001 From: Marcus Schiesser <mail@marcusschiesser.de> Date: Fri, 29 Dec 2023 14:10:01 +0700 Subject: [PATCH] refactor: separate python and typescript generators --- packages/create-llama/templates/index.ts | 328 +----------------- packages/create-llama/templates/python.ts | 114 ++++++ packages/create-llama/templates/typescript.ts | 217 ++++++++++++ 3 files changed, 334 insertions(+), 325 deletions(-) create mode 100644 packages/create-llama/templates/python.ts create mode 100644 packages/create-llama/templates/typescript.ts diff --git a/packages/create-llama/templates/index.ts b/packages/create-llama/templates/index.ts index 4508b24b1..973a65b85 100644 --- a/packages/create-llama/templates/index.ts +++ b/packages/create-llama/templates/index.ts @@ -1,22 +1,21 @@ -import { parse, stringify } from "smol-toml"; import { copy } from "../helpers/copy"; import { callPackageManager } from "../helpers/install"; import fs from "fs/promises"; -import os from "os"; import path from "path"; -import { bold, cyan } from "picocolors"; -import { version } from "../../core/package.json"; +import { cyan } from "picocolors"; import { COMMUNITY_OWNER, COMMUNITY_REPO } from "../helpers/constant"; import { PackageManager } from "../helpers/get-pkg-manager"; import { downloadAndExtractRepo } from "../helpers/repo"; +import { installPythonTemplate } from "./python"; import { InstallTemplateArgs, TemplateEngine, TemplateFramework, TemplateVectorDB, } from "./types"; +import { installTSTemplate } from "./typescript"; const createEnvLocalFile = async ( root: string, @@ -103,327 +102,6 @@ const copyTestData = async ( } }; -const rename = (name: string) => { - switch (name) { - case "gitignore": - case "eslintrc.json": { - return `.${name}`; - } - // README.md is ignored by webpack-asset-relocator-loader used by ncc: - // https://github.com/vercel/webpack-asset-relocator-loader/blob/e9308683d47ff507253e37c9bcbb99474603192b/src/asset-relocator.js#L227 - case "README-template.md": { - return "README.md"; - } - default: { - return name; - } - } -}; - -/** - * Install a LlamaIndex internal template to a given `root` directory. - */ -const installTSTemplate = async ({ - appName, - root, - packageManager, - isOnline, - template, - framework, - engine, - ui, - eslint, - customApiPath, - forBackend, - model, - vectorDb, -}: InstallTemplateArgs) => { - console.log(bold(`Using ${packageManager}.`)); - - /** - * Copy the template files to the target directory. - */ - console.log("\nInitializing project with template:", template, "\n"); - const templatePath = path.join(__dirname, "types", template, framework); - const copySource = ["**"]; - if (!eslint) copySource.push("!eslintrc.json"); - - await copy(copySource, root, { - parents: true, - cwd: templatePath, - rename, - }); - - /** - * If the backend is next.js, rename next.config.app.js to next.config.js - * If not, rename next.config.static.js to next.config.js - */ - if (framework == "nextjs" && forBackend === "nextjs") { - const nextConfigAppPath = path.join(root, "next.config.app.js"); - const nextConfigPath = path.join(root, "next.config.js"); - await fs.rename(nextConfigAppPath, nextConfigPath); - // delete next.config.static.js - const nextConfigStaticPath = path.join(root, "next.config.static.js"); - await fs.rm(nextConfigStaticPath); - } else if (framework == "nextjs" && typeof forBackend === "undefined") { - const nextConfigStaticPath = path.join(root, "next.config.static.js"); - const nextConfigPath = path.join(root, "next.config.js"); - await fs.rename(nextConfigStaticPath, nextConfigPath); - // delete next.config.app.js - const nextConfigAppPath = path.join(root, "next.config.app.js"); - await fs.rm(nextConfigAppPath); - } - - /** - * Copy the selected chat engine files to the target directory and reference it. - */ - let relativeEngineDestPath; - const compPath = path.join(__dirname, "components"); - if (engine && (framework === "express" || framework === "nextjs")) { - console.log("\nUsing chat engine:", engine, "\n"); - - let vectorDBFolder: string = engine; - - if (engine !== "simple" && vectorDb) { - console.log("\nUsing vector DB:", vectorDb, "\n"); - vectorDBFolder = vectorDb; - } - - const VectorDBPath = path.join( - compPath, - "vectordbs", - "typescript", - vectorDBFolder, - ); - relativeEngineDestPath = - framework === "nextjs" - ? path.join("app", "api", "chat") - : path.join("src", "controllers"); - await copy("**", path.join(root, relativeEngineDestPath, "engine"), { - parents: true, - cwd: VectorDBPath, - }); - } - - /** - * Copy the selected UI files to the target directory and reference it. - */ - if (framework === "nextjs" && ui !== "shadcn") { - console.log("\nUsing UI:", ui, "\n"); - const uiPath = path.join(compPath, "ui", ui); - const destUiPath = path.join(root, "app", "components", "ui"); - // remove the default ui folder - await fs.rm(destUiPath, { recursive: true }); - // copy the selected ui folder - await copy("**", destUiPath, { - parents: true, - cwd: uiPath, - rename, - }); - } - - /** - * Update the package.json scripts. - */ - const packageJsonFile = path.join(root, "package.json"); - const packageJson: any = JSON.parse( - await fs.readFile(packageJsonFile, "utf8"), - ); - packageJson.name = appName; - packageJson.version = "0.1.0"; - - packageJson.dependencies = { - ...packageJson.dependencies, - llamaindex: version, - }; - - if (framework === "nextjs" && customApiPath) { - console.log( - "\nUsing external API with custom API path:", - customApiPath, - "\n", - ); - // remove the default api folder - const apiPath = path.join(root, "app", "api"); - await fs.rm(apiPath, { recursive: true }); - // modify the dev script to use the custom api path - packageJson.scripts = { - ...packageJson.scripts, - dev: `cross-env NEXT_PUBLIC_CHAT_API=${customApiPath} next dev`, - }; - } - - if (engine === "context" && relativeEngineDestPath) { - // add generate script if using context engine - packageJson.scripts = { - ...packageJson.scripts, - generate: `node ${path.join( - relativeEngineDestPath, - "engine", - "generate.mjs", - )}`, - }; - } - - if (framework === "nextjs" && ui === "html") { - // remove shadcn dependencies if html ui is selected - packageJson.dependencies = { - ...packageJson.dependencies, - "tailwind-merge": undefined, - "@radix-ui/react-slot": undefined, - "class-variance-authority": undefined, - clsx: undefined, - "lucide-react": undefined, - remark: undefined, - "remark-code-import": undefined, - "remark-gfm": undefined, - "remark-math": undefined, - "react-markdown": undefined, - "react-syntax-highlighter": undefined, - }; - - packageJson.devDependencies = { - ...packageJson.devDependencies, - "@types/react-syntax-highlighter": undefined, - }; - } - - if (!eslint) { - // Remove packages starting with "eslint" from devDependencies - packageJson.devDependencies = Object.fromEntries( - Object.entries(packageJson.devDependencies).filter( - ([key]) => !key.startsWith("eslint"), - ), - ); - } - await fs.writeFile( - packageJsonFile, - JSON.stringify(packageJson, null, 2) + os.EOL, - ); - - console.log("\nInstalling dependencies:"); - for (const dependency in packageJson.dependencies) - console.log(`- ${cyan(dependency)}`); - - console.log("\nInstalling devDependencies:"); - for (const dependency in packageJson.devDependencies) - console.log(`- ${cyan(dependency)}`); - - console.log(); - - await callPackageManager(packageManager, isOnline); -}; - -interface IDependencyItem { - name: string; - version: string; -} - -const getPythonAddOnDependencies = (vectorDb?: TemplateVectorDB) => { - const addOnDependencies: IDependencyItem[] = []; - - switch (vectorDb) { - case "mongo": { - addOnDependencies.push({ - name: "pymongo", - version: "^4.6.1", - }); - break; - } - } - - return addOnDependencies; -}; - -const preparePythonDependencies = async ( - root: string, - addOnDependencies: IDependencyItem[], -) => { - if (addOnDependencies.length === 0) return; - - const FILENAME = "pyproject.toml"; - try { - // Parse toml file - const file = path.join(root, FILENAME); - const fileContent = await fs.readFile(file, "utf8"); - const fileParsed = parse(fileContent); - - // Modify toml dependencies - const tool = fileParsed.tool as any; - const dependencies = tool.poetry.dependencies as any; - for (const dependency of addOnDependencies) { - dependencies[dependency.name] = dependency.version; - } - - // Write toml file - const newFileContent = stringify(fileParsed); - await fs.writeFile(file, newFileContent); - - const dependenciesString = addOnDependencies.map((d) => d.name).join(", "); - console.log(`\nAdded ${dependenciesString} to ${cyan(FILENAME)}\n`); - } catch (error) { - console.log( - `Error when preparing ${FILENAME} file for Python template\n`, - error, - ); - console.log(error); - } -}; - -const installPythonTemplate = async ({ - root, - template, - framework, - engine, - vectorDb, -}: Pick< - InstallTemplateArgs, - "root" | "framework" | "template" | "engine" | "vectorDb" ->) => { - console.log("\nInitializing Python project with template:", template, "\n"); - const templatePath = path.join(__dirname, "types", template, framework); - await copy("**", root, { - parents: true, - cwd: templatePath, - rename(name) { - switch (name) { - case "gitignore": { - return `.${name}`; - } - // README.md is ignored by webpack-asset-relocator-loader used by ncc: - // https://github.com/vercel/webpack-asset-relocator-loader/blob/e9308683d47ff507253e37c9bcbb99474603192b/src/asset-relocator.js#L227 - case "README-template.md": { - return "README.md"; - } - default: { - return name; - } - } - }, - }); - - if (engine === "context") { - const compPath = path.join(__dirname, "components"); - const VectorDBPath = path.join( - compPath, - "vectordbs", - "python", - vectorDb || "none", - ); - await copy("**", path.join(root, "app", "engine"), { - parents: true, - cwd: VectorDBPath, - }); - } - - const addOnDependencies = getPythonAddOnDependencies(vectorDb); - await preparePythonDependencies(root, addOnDependencies); - - console.log( - "\nPython project, dependencies won't be installed automatically.\n", - ); -}; - const installCommunityProject = async ({ root, communityProjectPath, diff --git a/packages/create-llama/templates/python.ts b/packages/create-llama/templates/python.ts new file mode 100644 index 000000000..f845615c7 --- /dev/null +++ b/packages/create-llama/templates/python.ts @@ -0,0 +1,114 @@ +import fs from "fs/promises"; +import path from "path"; +import { cyan } from "picocolors"; +import { parse, stringify } from "smol-toml"; +import { copy } from "../helpers/copy"; +import { InstallTemplateArgs, TemplateVectorDB } from "./types"; + +interface IDependencyItem { + name: string; + version: string; +} + +const getPythonAddOnDependencies = (vectorDb?: TemplateVectorDB) => { + const addOnDependencies: IDependencyItem[] = []; + + switch (vectorDb) { + case "mongo": { + addOnDependencies.push({ + name: "pymongo", + version: "^4.6.1", + }); + break; + } + } + + return addOnDependencies; +}; +const preparePythonDependencies = async ( + root: string, + addOnDependencies: IDependencyItem[], +) => { + if (addOnDependencies.length === 0) return; + + const FILENAME = "pyproject.toml"; + try { + // Parse toml file + const file = path.join(root, FILENAME); + const fileContent = await fs.readFile(file, "utf8"); + const fileParsed = parse(fileContent); + + // Modify toml dependencies + const tool = fileParsed.tool as any; + const dependencies = tool.poetry.dependencies as any; + for (const dependency of addOnDependencies) { + dependencies[dependency.name] = dependency.version; + } + + // Write toml file + const newFileContent = stringify(fileParsed); + await fs.writeFile(file, newFileContent); + + const dependenciesString = addOnDependencies.map((d) => d.name).join(", "); + console.log(`\nAdded ${dependenciesString} to ${cyan(FILENAME)}\n`); + } catch (error) { + console.log( + `Error when preparing ${FILENAME} file for Python template\n`, + error, + ); + console.log(error); + } +}; +export const installPythonTemplate = async ({ + root, + template, + framework, + engine, + vectorDb, +}: Pick< + InstallTemplateArgs, + "root" | "framework" | "template" | "engine" | "vectorDb" +>) => { + console.log("\nInitializing Python project with template:", template, "\n"); + const templatePath = path.join(__dirname, "types", template, framework); + await copy("**", root, { + parents: true, + cwd: templatePath, + rename(name) { + switch (name) { + case "gitignore": { + return `.${name}`; + } + // README.md is ignored by webpack-asset-relocator-loader used by ncc: + // https://github.com/vercel/webpack-asset-relocator-loader/blob/e9308683d47ff507253e37c9bcbb99474603192b/src/asset-relocator.js#L227 + case "README-template.md": { + return "README.md"; + } + default: { + return name; + } + } + }, + }); + + if (engine === "context") { + const compPath = path.join(__dirname, "components"); + const VectorDBPath = path.join( + compPath, + "vectordbs", + "python", + vectorDb || "none", + ); + await copy("**", path.join(root, "app", "engine"), { + parents: true, + cwd: VectorDBPath, + }); + } + + const addOnDependencies = getPythonAddOnDependencies(vectorDb); + await preparePythonDependencies(root, addOnDependencies); + + console.log( + "\nPython project, dependencies won't be installed automatically.\n", + ); +}; diff --git a/packages/create-llama/templates/typescript.ts b/packages/create-llama/templates/typescript.ts new file mode 100644 index 000000000..df3c0b15b --- /dev/null +++ b/packages/create-llama/templates/typescript.ts @@ -0,0 +1,217 @@ +import fs from "fs/promises"; +import os from "os"; +import path from "path"; +import { bold, cyan } from "picocolors"; +import { version } from "../../core/package.json"; +import { copy } from "../helpers/copy"; +import { callPackageManager } from "../helpers/install"; +import { InstallTemplateArgs } from "./types"; + +const rename = (name: string) => { + switch (name) { + case "gitignore": + case "eslintrc.json": { + return `.${name}`; + } + // README.md is ignored by webpack-asset-relocator-loader used by ncc: + // https://github.com/vercel/webpack-asset-relocator-loader/blob/e9308683d47ff507253e37c9bcbb99474603192b/src/asset-relocator.js#L227 + case "README-template.md": { + return "README.md"; + } + default: { + return name; + } + } +}; +/** + * Install a LlamaIndex internal template to a given `root` directory. + */ +export const installTSTemplate = async ({ + appName, + root, + packageManager, + isOnline, + template, + framework, + engine, + ui, + eslint, + customApiPath, + forBackend, + vectorDb, +}: InstallTemplateArgs) => { + console.log(bold(`Using ${packageManager}.`)); + + /** + * Copy the template files to the target directory. + */ + console.log("\nInitializing project with template:", template, "\n"); + const templatePath = path.join(__dirname, "types", template, framework); + const copySource = ["**"]; + if (!eslint) copySource.push("!eslintrc.json"); + + await copy(copySource, root, { + parents: true, + cwd: templatePath, + rename, + }); + + /** + * If the backend is next.js, rename next.config.app.js to next.config.js + * If not, rename next.config.static.js to next.config.js + */ + if (framework == "nextjs" && forBackend === "nextjs") { + const nextConfigAppPath = path.join(root, "next.config.app.js"); + const nextConfigPath = path.join(root, "next.config.js"); + await fs.rename(nextConfigAppPath, nextConfigPath); + // delete next.config.static.js + const nextConfigStaticPath = path.join(root, "next.config.static.js"); + await fs.rm(nextConfigStaticPath); + } else if (framework == "nextjs" && typeof forBackend === "undefined") { + const nextConfigStaticPath = path.join(root, "next.config.static.js"); + const nextConfigPath = path.join(root, "next.config.js"); + await fs.rename(nextConfigStaticPath, nextConfigPath); + // delete next.config.app.js + const nextConfigAppPath = path.join(root, "next.config.app.js"); + await fs.rm(nextConfigAppPath); + } + + /** + * Copy the selected chat engine files to the target directory and reference it. + */ + let relativeEngineDestPath; + const compPath = path.join(__dirname, "components"); + if (engine && (framework === "express" || framework === "nextjs")) { + console.log("\nUsing chat engine:", engine, "\n"); + + let vectorDBFolder: string = engine; + + if (engine !== "simple" && vectorDb) { + console.log("\nUsing vector DB:", vectorDb, "\n"); + vectorDBFolder = vectorDb; + } + + const VectorDBPath = path.join( + compPath, + "vectordbs", + "typescript", + vectorDBFolder, + ); + relativeEngineDestPath = + framework === "nextjs" + ? path.join("app", "api", "chat") + : path.join("src", "controllers"); + await copy("**", path.join(root, relativeEngineDestPath, "engine"), { + parents: true, + cwd: VectorDBPath, + }); + } + + /** + * Copy the selected UI files to the target directory and reference it. + */ + if (framework === "nextjs" && ui !== "shadcn") { + console.log("\nUsing UI:", ui, "\n"); + const uiPath = path.join(compPath, "ui", ui); + const destUiPath = path.join(root, "app", "components", "ui"); + // remove the default ui folder + await fs.rm(destUiPath, { recursive: true }); + // copy the selected ui folder + await copy("**", destUiPath, { + parents: true, + cwd: uiPath, + rename, + }); + } + + /** + * Update the package.json scripts. + */ + const packageJsonFile = path.join(root, "package.json"); + const packageJson: any = JSON.parse( + await fs.readFile(packageJsonFile, "utf8"), + ); + packageJson.name = appName; + packageJson.version = "0.1.0"; + + packageJson.dependencies = { + ...packageJson.dependencies, + llamaindex: version, + }; + + if (framework === "nextjs" && customApiPath) { + console.log( + "\nUsing external API with custom API path:", + customApiPath, + "\n", + ); + // remove the default api folder + const apiPath = path.join(root, "app", "api"); + await fs.rm(apiPath, { recursive: true }); + // modify the dev script to use the custom api path + packageJson.scripts = { + ...packageJson.scripts, + dev: `cross-env NEXT_PUBLIC_CHAT_API=${customApiPath} next dev`, + }; + } + + if (engine === "context" && relativeEngineDestPath) { + // add generate script if using context engine + packageJson.scripts = { + ...packageJson.scripts, + generate: `node ${path.join( + relativeEngineDestPath, + "engine", + "generate.mjs", + )}`, + }; + } + + if (framework === "nextjs" && ui === "html") { + // remove shadcn dependencies if html ui is selected + packageJson.dependencies = { + ...packageJson.dependencies, + "tailwind-merge": undefined, + "@radix-ui/react-slot": undefined, + "class-variance-authority": undefined, + clsx: undefined, + "lucide-react": undefined, + remark: undefined, + "remark-code-import": undefined, + "remark-gfm": undefined, + "remark-math": undefined, + "react-markdown": undefined, + "react-syntax-highlighter": undefined, + }; + + packageJson.devDependencies = { + ...packageJson.devDependencies, + "@types/react-syntax-highlighter": undefined, + }; + } + + if (!eslint) { + // Remove packages starting with "eslint" from devDependencies + packageJson.devDependencies = Object.fromEntries( + Object.entries(packageJson.devDependencies).filter( + ([key]) => !key.startsWith("eslint"), + ), + ); + } + await fs.writeFile( + packageJsonFile, + JSON.stringify(packageJson, null, 2) + os.EOL, + ); + + console.log("\nInstalling dependencies:"); + for (const dependency in packageJson.dependencies) + console.log(`- ${cyan(dependency)}`); + + console.log("\nInstalling devDependencies:"); + for (const dependency in packageJson.devDependencies) + console.log(`- ${cyan(dependency)}`); + + console.log(); + + await callPackageManager(packageManager, isOnline); +}; -- GitLab