From 4371c46c4c204468051f9b664238cd566f11e45c Mon Sep 17 00:00:00 2001
From: Marcus Schiesser <mail@marcusschiesser.de>
Date: Thu, 26 Oct 2023 16:33:02 +0700
Subject: [PATCH] add express example, framework selector and use existing
 package.json (just update it)

---
 packages/create-llama/create-app.ts           | 10 +-
 packages/create-llama/index.ts                | 31 +++++-
 packages/create-llama/templates/index.ts      | 99 ++++++-------------
 .../simple/express/README-template.md         | 19 ++++
 .../templates/simple/express/eslintrc.json    |  3 +
 .../templates/simple/express/index.ts         | 17 ++++
 .../templates/simple/express/package.json     | 22 +++++
 .../express/src/controllers/llm.controller.ts | 42 ++++++++
 .../simple/express/src/routes/llm.route.ts    |  8 ++
 .../templates/simple/express/tsconfig.json    | 11 +++
 .../templates/simple/nextjs/package.json      | 27 +++++
 packages/create-llama/templates/types.ts      |  8 +-
 12 files changed, 213 insertions(+), 84 deletions(-)
 create mode 100644 packages/create-llama/templates/simple/express/README-template.md
 create mode 100644 packages/create-llama/templates/simple/express/eslintrc.json
 create mode 100644 packages/create-llama/templates/simple/express/index.ts
 create mode 100644 packages/create-llama/templates/simple/express/package.json
 create mode 100644 packages/create-llama/templates/simple/express/src/controllers/llm.controller.ts
 create mode 100644 packages/create-llama/templates/simple/express/src/routes/llm.route.ts
 create mode 100644 packages/create-llama/templates/simple/express/tsconfig.json
 create mode 100644 packages/create-llama/templates/simple/nextjs/package.json

diff --git a/packages/create-llama/create-app.ts b/packages/create-llama/create-app.ts
index 18e181179..8efef515f 100644
--- a/packages/create-llama/create-app.ts
+++ b/packages/create-llama/create-app.ts
@@ -8,25 +8,24 @@ import { getOnline } from "./helpers/is-online";
 import { isWriteable } from "./helpers/is-writeable";
 import { makeDir } from "./helpers/make-dir";
 
-import type { TemplateMode, TemplateType } from "./templates";
+import type { TemplateFramework, TemplateType } from "./templates";
 import { installTemplate } from "./templates";
 
 export async function createApp({
+  framework,
   appPath,
   packageManager,
-  tailwind,
   eslint,
   srcDir,
   importAlias,
 }: {
+  framework: TemplateFramework;
   appPath: string;
   packageManager: PackageManager;
-  tailwind: boolean;
   eslint: boolean;
   srcDir: boolean;
   importAlias: string;
 }): Promise<void> {
-  const mode: TemplateMode = "nextjs";
   const template: TemplateType = "simple";
 
   const root = path.resolve(appPath);
@@ -64,10 +63,9 @@ export async function createApp({
     appName,
     root,
     template,
-    mode,
+    framework,
     packageManager,
     isOnline,
-    tailwind,
     eslint,
     srcDir,
     importAlias,
diff --git a/packages/create-llama/index.ts b/packages/create-llama/index.ts
index 01fac2d50..9ff2ee674 100644
--- a/packages/create-llama/index.ts
+++ b/packages/create-llama/index.ts
@@ -177,8 +177,8 @@ async function run(): Promise<void> {
   >;
 
   const defaults: typeof preferences = {
+    framework: "nextjs",
     eslint: true,
-    tailwind: true,
     app: true,
     srcDir: false,
     importAlias: "@/*",
@@ -187,6 +187,33 @@ async function run(): Promise<void> {
   const getPrefOrDefault = (field: string) =>
     preferences[field] ?? defaults[field];
 
+  if (!program.framework) {
+    if (ciInfo.isCI) {
+      program.framework = getPrefOrDefault("framework");
+    } else {
+      const { framework } = await prompts(
+        {
+          type: "select",
+          name: "framework",
+          message: "Which framework would you like to use?",
+          choices: [
+            { title: "NextJS", value: "nextjs" },
+            { title: "Express", value: "express" },
+          ],
+          initial: 0,
+        },
+        {
+          onCancel: () => {
+            console.error("Exiting.");
+            process.exit(1);
+          },
+        },
+      );
+      program.framework = framework;
+      preferences.framework = framework;
+    }
+  }
+
   if (
     !process.argv.includes("--eslint") &&
     !process.argv.includes("--no-eslint")
@@ -248,9 +275,9 @@ async function run(): Promise<void> {
   }
 
   await createApp({
+    framework: program.framework,
     appPath: resolvedProjectPath,
     packageManager,
-    tailwind: true,
     eslint: program.eslint,
     srcDir: program.srcDir,
     importAlias: program.importAlias,
diff --git a/packages/create-llama/templates/index.ts b/packages/create-llama/templates/index.ts
index ca013db06..d9de01f8d 100644
--- a/packages/create-llama/templates/index.ts
+++ b/packages/create-llama/templates/index.ts
@@ -8,21 +8,19 @@ import fs from "fs/promises";
 import os from "os";
 import path from "path";
 import { bold, cyan } from "picocolors";
-import { version } from "../package.json"
+import { version } from "../package.json";
 
 import { GetTemplateFileArgs, InstallTemplateArgs } from "./types";
 
-const NEXT_VERSION = "13.5.6";
-
 /**
  * Get the file path for a given file in a template, e.g. "next.config.js".
  */
 export const getTemplateFile = ({
   template,
-  mode,
+  framework,
   file,
 }: GetTemplateFileArgs): string => {
-  return path.join(__dirname, template, mode, file);
+  return path.join(__dirname, template, framework, file);
 };
 
 export const SRC_DIR_NAMES = ["app", "pages", "styles"];
@@ -36,8 +34,7 @@ export const installTemplate = async ({
   packageManager,
   isOnline,
   template,
-  mode,
-  tailwind,
+  framework,
   eslint,
   srcDir,
   importAlias,
@@ -48,14 +45,9 @@ export const installTemplate = async ({
    * Copy the template files to the target directory.
    */
   console.log("\nInitializing project with template:", template, "\n");
-  const templatePath = path.join(__dirname, template, mode);
+  const templatePath = path.join(__dirname, template, framework);
   const copySource = ["**"];
   if (!eslint) copySource.push("!eslintrc.json");
-  if (!tailwind)
-    copySource.push(
-      mode == "nextjs" ? "tailwind.config.ts" : "!tailwind.config.js",
-      "!postcss.config.js",
-    );
 
   await copy(copySource, root, {
     parents: true,
@@ -148,7 +140,7 @@ export const installTemplate = async ({
       ),
     );
 
-    if (tailwind) {
+    if (framework === "nextjs") {
       const tailwindConfigFile = path.join(root, "tailwind.config.ts");
       await fs.writeFile(
         tailwindConfigFile,
@@ -160,64 +152,31 @@ export const installTemplate = async ({
     }
   }
 
-  /** Create a package.json for the new project and write it to disk. */
-  const packageJson: any = {
-    name: appName,
-    version: "0.1.0",
-    private: true,
-    scripts: {
-      dev: "next dev",
-      build: "next build",
-      start: "next start",
-      lint: "next lint",
-    },
-    /**
-     * Default dependencies.
-     */
-    dependencies: {
-      react: "^18",
-      "react-dom": "^18",
-      next: NEXT_VERSION,
-      llamaindex: version,
-    },
-    devDependencies: {},
-  };
-
   /**
-   * TypeScript projects will have type definitions and other devDependencies.
+   * Update the package.json scripts.
    */
-  packageJson.devDependencies = {
-    ...packageJson.devDependencies,
-    typescript: "^5",
-    "@types/node": "^20",
-    "@types/react": "^18",
-    "@types/react-dom": "^18",
-  };
+  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";
 
-  /* Add Tailwind CSS dependencies. */
-  if (tailwind) {
-    packageJson.devDependencies = {
-      ...packageJson.devDependencies,
-      autoprefixer: "^10",
-      postcss: "^8",
-      tailwindcss: "^3",
-    };
-  }
+  packageJson.dependencies = {
+    ...packageJson.dependencies,
+    llamaindex: version,
+  };
 
-  /* Default ESLint dependencies. */
-  if (eslint) {
-    packageJson.devDependencies = {
-      ...packageJson.devDependencies,
-      eslint: "^8",
-      "eslint-config-next": NEXT_VERSION,
-    };
+  if (!eslint) {
+    // Remove packages starting with "eslint" from devDependencies
+    packageJson.devDependencies = Object.fromEntries(
+      Object.entries(packageJson.devDependencies).filter(
+        ([key]) => !key.startsWith("eslint"),
+      ),
+    );
   }
-
-  const devDeps = Object.keys(packageJson.devDependencies).length;
-  if (!devDeps) delete packageJson.devDependencies;
-
   await fs.writeFile(
-    path.join(root, "package.json"),
+    packageJsonFile,
     JSON.stringify(packageJson, null, 2) + os.EOL,
   );
 
@@ -225,11 +184,9 @@ export const installTemplate = async ({
   for (const dependency in packageJson.dependencies)
     console.log(`- ${cyan(dependency)}`);
 
-  if (devDeps) {
-    console.log("\nInstalling devDependencies:");
-    for (const dependency in packageJson.devDependencies)
-      console.log(`- ${cyan(dependency)}`);
-  }
+  console.log("\nInstalling devDependencies:");
+  for (const dependency in packageJson.devDependencies)
+    console.log(`- ${cyan(dependency)}`);
 
   console.log();
 
diff --git a/packages/create-llama/templates/simple/express/README-template.md b/packages/create-llama/templates/simple/express/README-template.md
new file mode 100644
index 000000000..b00c2e8d8
--- /dev/null
+++ b/packages/create-llama/templates/simple/express/README-template.md
@@ -0,0 +1,19 @@
+1. Install the dependencies
+```
+pnpm install
+```
+
+2. Run the server
+```
+pnpm run dev
+```
+
+3. Call the API to LLM Chat
+```
+curl --location 'localhost:3000/api/llm' \
+--header 'Content-Type: application/json' \
+--data '{
+    "message": "Hello",
+    "chatHistory": []
+}'
+```
\ No newline at end of file
diff --git a/packages/create-llama/templates/simple/express/eslintrc.json b/packages/create-llama/templates/simple/express/eslintrc.json
new file mode 100644
index 000000000..c19581799
--- /dev/null
+++ b/packages/create-llama/templates/simple/express/eslintrc.json
@@ -0,0 +1,3 @@
+{
+  "extends": "eslint:recommended"
+}
\ No newline at end of file
diff --git a/packages/create-llama/templates/simple/express/index.ts b/packages/create-llama/templates/simple/express/index.ts
new file mode 100644
index 000000000..157555572
--- /dev/null
+++ b/packages/create-llama/templates/simple/express/index.ts
@@ -0,0 +1,17 @@
+import express, { Express, Request, Response } from "express";
+import llmRouter from "./src/routes/llm.route";
+
+const app: Express = express();
+const port = 3000;
+
+app.use(express.json());
+
+app.get("/", (req: Request, res: Response) => {
+  res.send("LlamaIndex Express Server");
+});
+
+app.use("/api/llm", llmRouter);
+
+app.listen(port, () => {
+  console.log(`⚡️[server]: Server is running at http://localhost:${port}`);
+});
diff --git a/packages/create-llama/templates/simple/express/package.json b/packages/create-llama/templates/simple/express/package.json
new file mode 100644
index 000000000..3b57d610e
--- /dev/null
+++ b/packages/create-llama/templates/simple/express/package.json
@@ -0,0 +1,22 @@
+{
+  "name": "llama-index-express",
+  "version": "1.0.0",
+  "main": "index.js",
+  "scripts": {
+    "build": "tsc",
+    "start": "node dist/index.js",
+    "dev": "concurrently \"tsc --watch\" \"nodemon -q dist/index.js\""
+  },
+  "dependencies": {
+    "express": "^4",
+    "llamaindex": "0.0.31"
+  },
+  "devDependencies": {
+    "@types/express": "^4",
+    "@types/node": "^20",
+    "concurrently": "^8",
+    "nodemon": "^3",
+    "typescript": "^5",
+    "eslint": "^8"
+  }
+}
\ No newline at end of file
diff --git a/packages/create-llama/templates/simple/express/src/controllers/llm.controller.ts b/packages/create-llama/templates/simple/express/src/controllers/llm.controller.ts
new file mode 100644
index 000000000..54fa51b13
--- /dev/null
+++ b/packages/create-llama/templates/simple/express/src/controllers/llm.controller.ts
@@ -0,0 +1,42 @@
+import { ChatMessage, OpenAI, SimpleChatEngine } from "llamaindex";
+import { NextFunction, Request, Response } from "express";
+
+export const chat = async (req: Request, res: Response, next: NextFunction) => {
+	try {
+		const {
+			message,
+			chatHistory,
+		}: {
+			message: string;
+			chatHistory: ChatMessage[];
+		} = req.body;
+		if (!message || !chatHistory) {
+			return res.status(400).json({
+				error: "message, chatHistory are required in the request body",
+			});
+		}
+
+		const llm = new OpenAI({
+			model: "gpt-3.5-turbo",
+		});
+
+		const chatEngine = new SimpleChatEngine({
+			llm,
+		});
+
+		const response = await chatEngine.chat(message, chatHistory);
+		const result: ChatMessage = {
+			role: "assistant",
+			content: response.response,
+		};
+
+		return res.status(200).json({
+			result,
+		});
+	} catch (error) {
+		console.error("[LlamaIndex]", error);
+		return res.status(500).json({
+			error: (error as Error).message,
+		});
+	}
+};
diff --git a/packages/create-llama/templates/simple/express/src/routes/llm.route.ts b/packages/create-llama/templates/simple/express/src/routes/llm.route.ts
new file mode 100644
index 000000000..3711c71b9
--- /dev/null
+++ b/packages/create-llama/templates/simple/express/src/routes/llm.route.ts
@@ -0,0 +1,8 @@
+import express from "express";
+import { chat } from "../controllers/llm.controller";
+
+const llmRouter = express.Router();
+
+llmRouter.route("/").post(chat);
+
+export default llmRouter;
diff --git a/packages/create-llama/templates/simple/express/tsconfig.json b/packages/create-llama/templates/simple/express/tsconfig.json
new file mode 100644
index 000000000..fd70902d6
--- /dev/null
+++ b/packages/create-llama/templates/simple/express/tsconfig.json
@@ -0,0 +1,11 @@
+{
+  "compilerOptions": {
+    "target": "es2016",
+    "module": "commonjs",
+    "outDir": "./dist",
+    "esModuleInterop": true,
+    "forceConsistentCasingInFileNames": true,
+    "strict": true,
+    "skipLibCheck": true
+  }
+}
diff --git a/packages/create-llama/templates/simple/nextjs/package.json b/packages/create-llama/templates/simple/nextjs/package.json
new file mode 100644
index 000000000..afb59904c
--- /dev/null
+++ b/packages/create-llama/templates/simple/nextjs/package.json
@@ -0,0 +1,27 @@
+{
+  "name": "llama-index-nextjs",
+  "version": "1.0.0",
+  "scripts": {
+    "dev": "next dev",
+    "build": "next build",
+    "start": "next start",
+    "lint": "next lint"
+  },
+  "dependencies": {
+    "react": "^18",
+    "react-dom": "^18",
+    "next": "^13",
+    "llamaindex": "0.0.31"
+  },
+  "devDependencies": {
+    "typescript": "^5",
+    "@types/node": "^20",
+    "@types/react": "^18",
+    "@types/react-dom": "^18",
+    "autoprefixer": "^10",
+    "postcss": "^8",
+    "tailwindcss": "^3",
+    "eslint": "^8",
+    "eslint-config-next": "^13"
+  }
+}
\ No newline at end of file
diff --git a/packages/create-llama/templates/types.ts b/packages/create-llama/templates/types.ts
index 5312d80dd..9cd44c968 100644
--- a/packages/create-llama/templates/types.ts
+++ b/packages/create-llama/templates/types.ts
@@ -1,11 +1,11 @@
 import { PackageManager } from "../helpers/get-pkg-manager";
 
 export type TemplateType = "simple";
-export type TemplateMode = "nextjs";
+export type TemplateFramework = "nextjs" | "express";
 
 export interface GetTemplateFileArgs {
   template: TemplateType;
-  mode: TemplateMode;
+  framework: TemplateFramework;
   file: string;
 }
 
@@ -14,11 +14,9 @@ export interface InstallTemplateArgs {
   root: string;
   packageManager: PackageManager;
   isOnline: boolean;
-
   template: TemplateType;
-  mode: TemplateMode;
+  framework: TemplateFramework;
   eslint: boolean;
-  tailwind: boolean;
   srcDir: boolean;
   importAlias: string;
 }
-- 
GitLab