Skip to content
Snippets Groups Projects
Commit 07fd79ec authored by Marcus Schiesser's avatar Marcus Schiesser
Browse files

feat: generate fullstack app with fastapi or express

parent 42ed0045
No related branches found
No related tags found
No related merge requests found
...@@ -7,8 +7,10 @@ import { getOnline } from "./helpers/is-online"; ...@@ -7,8 +7,10 @@ import { getOnline } from "./helpers/is-online";
import { isWriteable } from "./helpers/is-writeable"; import { isWriteable } from "./helpers/is-writeable";
import { makeDir } from "./helpers/make-dir"; import { makeDir } from "./helpers/make-dir";
import fs from "fs";
import terminalLink from "terminal-link";
import type { InstallTemplateArgs } from "./templates"; import type { InstallTemplateArgs } from "./templates";
import { installPythonTemplate, installTemplate } from "./templates"; import { installTemplate } from "./templates";
export async function createApp({ export async function createApp({
template, template,
...@@ -18,9 +20,13 @@ export async function createApp({ ...@@ -18,9 +20,13 @@ export async function createApp({
appPath, appPath,
packageManager, packageManager,
eslint, eslint,
customApiPath, frontend,
}: Omit<InstallTemplateArgs, "appName" | "root" | "isOnline"> & { }: Omit<
InstallTemplateArgs,
"appName" | "root" | "isOnline" | "customApiPath"
> & {
appPath: string; appPath: string;
frontend: boolean;
}): Promise<void> { }): Promise<void> {
const root = path.resolve(appPath); const root = path.resolve(appPath);
...@@ -47,30 +53,54 @@ export async function createApp({ ...@@ -47,30 +53,54 @@ export async function createApp({
console.log(`Creating a new LlamaIndex app in ${green(root)}.`); console.log(`Creating a new LlamaIndex app in ${green(root)}.`);
console.log(); console.log();
process.chdir(root); const args = {
appName,
root,
template,
framework,
engine,
ui,
packageManager,
isOnline,
eslint,
};
if (framework === "fastapi") { if (frontend) {
await installPythonTemplate({ appName, root, template, framework }); // install backend
} else { const backendRoot = path.join(root, "backend");
await makeDir(backendRoot);
await installTemplate({ ...args, root: backendRoot });
// install frontend
const frontendRoot = path.join(root, "frontend");
await makeDir(frontendRoot);
await installTemplate({ await installTemplate({
appName, ...args,
root, root: frontendRoot,
template, framework: "nextjs",
framework, customApiPath: "http://localhost:8000/api/chat",
engine,
ui,
packageManager,
isOnline,
eslint,
customApiPath,
}); });
// copy readme for fullstack
await fs.promises.copyFile(
path.join(__dirname, "templates", "README-fullstack.md"),
path.join(root, "README.md"),
);
} else {
await installTemplate(args);
} }
process.chdir(root);
if (tryGitInit(root)) { if (tryGitInit(root)) {
console.log("Initialized a git repository."); console.log("Initialized a git repository.");
console.log(); console.log();
} }
console.log(`${green("Success!")} Created ${appName} at ${appPath}`); console.log(`${green("Success!")} Created ${appName} at ${appPath}`);
console.log(
`Now have a look at the ${terminalLink(
"README.md",
"file://${appName}/README.md",
)} and learn how to get started.`,
);
console.log(); console.log();
} }
...@@ -11,7 +11,6 @@ import checkForUpdate from "update-check"; ...@@ -11,7 +11,6 @@ import checkForUpdate from "update-check";
import { createApp } from "./create-app"; import { createApp } from "./create-app";
import { getPkgManager } from "./helpers/get-pkg-manager"; import { getPkgManager } from "./helpers/get-pkg-manager";
import { isFolderEmpty } from "./helpers/is-folder-empty"; import { isFolderEmpty } from "./helpers/is-folder-empty";
import { isUrl } from "./helpers/is-url";
import { validateNpmName } from "./helpers/validate-pkg"; import { validateNpmName } from "./helpers/validate-pkg";
import packageJson from "./package.json"; import packageJson from "./package.json";
...@@ -167,7 +166,7 @@ async function run(): Promise<void> { ...@@ -167,7 +166,7 @@ async function run(): Promise<void> {
engine: "simple", engine: "simple",
ui: "html", ui: "html",
eslint: true, eslint: true,
customApiPath: "http://localhost:8000/api/chat", frontend: false,
}; };
const getPrefOrDefault = (field: string) => const getPrefOrDefault = (field: string) =>
preferences[field] ?? defaults[field]; preferences[field] ?? defaults[field];
...@@ -224,7 +223,36 @@ async function run(): Promise<void> { ...@@ -224,7 +223,36 @@ async function run(): Promise<void> {
} }
} }
if (program.framework === "nextjs") { if (program.framework === "express" || program.framework === "fastapi") {
// if a backend-only framework is selected, ask whether we should create a frontend
if (!program.frontend) {
if (ciInfo.isCI) {
program.frontend = getPrefOrDefault("frontend");
} else {
const styledNextJS = blue("NextJS");
const styledBackend = green(
program.framework === "express"
? "Express "
: program.framework === "fastapi"
? "FastAPI (Python) "
: "",
);
const { frontend } = await prompts({
onState: onPromptState,
type: "toggle",
name: "frontend",
message: `Would you like to generate a ${styledNextJS} frontend for your ${styledBackend}backend?`,
initial: getPrefOrDefault("frontend"),
active: "Yes",
inactive: "No",
});
program.frontend = Boolean(frontend);
preferences.frontend = Boolean(frontend);
}
}
}
if (program.framework === "nextjs" || program.frontend) {
if (!program.ui) { if (!program.ui) {
if (ciInfo.isCI) { if (ciInfo.isCI) {
program.ui = getPrefOrDefault("ui"); program.ui = getPrefOrDefault("ui");
...@@ -253,15 +281,6 @@ async function run(): Promise<void> { ...@@ -253,15 +281,6 @@ async function run(): Promise<void> {
if (ciInfo.isCI) { if (ciInfo.isCI) {
program.engine = getPrefOrDefault("engine"); program.engine = getPrefOrDefault("engine");
} else { } else {
const external =
program.framework === "nextjs"
? [
{
title: "External chat engine (e.g. FastAPI)",
value: "external",
},
]
: [];
const { engine } = await prompts( const { engine } = await prompts(
{ {
type: "select", type: "select",
...@@ -270,7 +289,6 @@ async function run(): Promise<void> { ...@@ -270,7 +289,6 @@ async function run(): Promise<void> {
choices: [ choices: [
{ title: "SimpleChatEngine", value: "simple" }, { title: "SimpleChatEngine", value: "simple" },
{ title: "ContextChatEngine", value: "context" }, { title: "ContextChatEngine", value: "context" },
...external,
], ],
initial: 0, initial: 0,
}, },
...@@ -280,29 +298,6 @@ async function run(): Promise<void> { ...@@ -280,29 +298,6 @@ async function run(): Promise<void> {
preferences.engine = engine; preferences.engine = engine;
} }
} }
if (
program.framework === "nextjs" &&
program.engine === "external" &&
!program.customApiPath
) {
if (ciInfo.isCI) {
program.customApiPath = getPrefOrDefault("customApiPath");
} else {
const { customApiPath } = await prompts(
{
type: "text",
name: "customApiPath",
message:
"URL path of your external chat engine (used for development)?",
validate: (url) => (isUrl(url) ? true : "Please enter a valid URL"),
initial: getPrefOrDefault("customApiPath"),
},
handlers,
);
program.customApiPath = customApiPath;
preferences.customApiPath = customApiPath;
}
}
} }
if ( if (
...@@ -336,7 +331,7 @@ async function run(): Promise<void> { ...@@ -336,7 +331,7 @@ async function run(): Promise<void> {
appPath: resolvedProjectPath, appPath: resolvedProjectPath,
packageManager, packageManager,
eslint: program.eslint, eslint: program.eslint,
customApiPath: program.customApiPath, frontend: program.frontend,
}); });
conf.set("preferences", preferences); conf.set("preferences", preferences);
} }
......
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
"@types/validate-npm-package-name": "3.0.0", "@types/validate-npm-package-name": "3.0.0",
"@vercel/ncc": "0.34.0", "@vercel/ncc": "0.34.0",
"async-retry": "1.3.1", "async-retry": "1.3.1",
"async-sema": "3.0.1",
"ci-info": "watson/ci-info#f43f6a1cefff47fb361c88cf4b943fdbcaafe540", "ci-info": "watson/ci-info#f43f6a1cefff47fb361c88cf4b943fdbcaafe540",
"commander": "2.20.0", "commander": "2.20.0",
"conf": "10.2.0", "conf": "10.2.0",
...@@ -50,7 +51,7 @@ ...@@ -50,7 +51,7 @@
"tar": "6.1.15", "tar": "6.1.15",
"update-check": "1.5.4", "update-check": "1.5.4",
"validate-npm-package-name": "3.0.0", "validate-npm-package-name": "3.0.0",
"async-sema": "3.0.1" "terminal-link": "^3.0.0"
}, },
"engines": { "engines": {
"node": ">=16.14.0" "node": ">=16.14.0"
......
This is a [LlamaIndex](https://www.llamaindex.ai/) project bootstrapped with [`create-llama`](https://github.com/run-llama/LlamaIndexTS/tree/main/packages/create-llama).
## Getting Started
First, startup the backend as described in the [backend README](./backend/README.md).
Second, run the development server of the frontend as described in the [frontend README](./frontend/README.md).
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
## Learn More
To learn more about LlamaIndex, take a look at the following resources:
- [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex (Python features).
- [LlamaIndexTS Documentation](https://ts.llamaindex.ai) - learn about LlamaIndex (Typescript features).
You can check out [the LlamaIndexTS GitHub repository](https://github.com/run-llama/LlamaIndexTS) - your feedback and contributions are welcome!
...@@ -7,12 +7,12 @@ import path from "path"; ...@@ -7,12 +7,12 @@ import path from "path";
import { bold, cyan } from "picocolors"; import { bold, cyan } from "picocolors";
import { version } from "../package.json"; import { version } from "../package.json";
import { InstallPythonTemplateArgs, InstallTemplateArgs } from "./types"; import { InstallTemplateArgs } from "./types";
/** /**
* Install a LlamaIndex internal template to a given `root` directory. * Install a LlamaIndex internal template to a given `root` directory.
*/ */
export const installTemplate = async ({ const installTSTemplate = async ({
appName, appName,
root, root,
packageManager, packageManager,
...@@ -60,7 +60,7 @@ export const installTemplate = async ({ ...@@ -60,7 +60,7 @@ export const installTemplate = async ({
*/ */
let relativeEngineDestPath; let relativeEngineDestPath;
const compPath = path.join(__dirname, "components"); const compPath = path.join(__dirname, "components");
if (framework === "express" || framework === "nextjs") { if (engine && (framework === "express" || framework === "nextjs")) {
console.log("\nUsing chat engine:", engine, "\n"); console.log("\nUsing chat engine:", engine, "\n");
const enginePath = path.join(compPath, "engines", engine); const enginePath = path.join(compPath, "engines", engine);
relativeEngineDestPath = relativeEngineDestPath =
...@@ -101,7 +101,7 @@ export const installTemplate = async ({ ...@@ -101,7 +101,7 @@ export const installTemplate = async ({
llamaindex: version, llamaindex: version,
}; };
if (engine === "external" && customApiPath) { if (framework === "nextjs" && customApiPath) {
console.log( console.log(
"\nUsing external API with custom API path:", "\nUsing external API with custom API path:",
customApiPath, customApiPath,
...@@ -166,12 +166,11 @@ export const installTemplate = async ({ ...@@ -166,12 +166,11 @@ export const installTemplate = async ({
await install(packageManager, isOnline); await install(packageManager, isOnline);
}; };
export const installPythonTemplate = async ({ const installPythonTemplate = async ({
appName,
root, root,
template, template,
framework, framework,
}: InstallPythonTemplateArgs) => { }: Pick<InstallTemplateArgs, "root" | "framework" | "template">) => {
console.log("\nInitializing Python project with template:", template, "\n"); console.log("\nInitializing Python project with template:", template, "\n");
const templatePath = path.join(__dirname, "types", template, framework); const templatePath = path.join(__dirname, "types", template, framework);
await copy("**", root, { await copy("**", root, {
...@@ -198,4 +197,13 @@ export const installPythonTemplate = async ({ ...@@ -198,4 +197,13 @@ export const installPythonTemplate = async ({
); );
}; };
export const installTemplate = async (props: InstallTemplateArgs) => {
process.chdir(props.root);
if (props.framework === "fastapi") {
await installPythonTemplate(props);
} else {
await installTSTemplate(props);
}
};
export * from "./types"; export * from "./types";
...@@ -2,16 +2,9 @@ import { PackageManager } from "../helpers/get-pkg-manager"; ...@@ -2,16 +2,9 @@ import { PackageManager } from "../helpers/get-pkg-manager";
export type TemplateType = "simple" | "streaming"; export type TemplateType = "simple" | "streaming";
export type TemplateFramework = "nextjs" | "express" | "fastapi"; export type TemplateFramework = "nextjs" | "express" | "fastapi";
export type TemplateEngine = "simple" | "context" | "external"; export type TemplateEngine = "simple" | "context";
export type TemplateUI = "html" | "shadcn"; export type TemplateUI = "html" | "shadcn";
export interface InstallPythonTemplateArgs {
appName: string;
root: string;
template: TemplateType;
framework: TemplateFramework;
}
export interface InstallTemplateArgs { export interface InstallTemplateArgs {
appName: string; appName: string;
root: string; root: string;
...@@ -19,8 +12,8 @@ export interface InstallTemplateArgs { ...@@ -19,8 +12,8 @@ export interface InstallTemplateArgs {
isOnline: boolean; isOnline: boolean;
template: TemplateType; template: TemplateType;
framework: TemplateFramework; framework: TemplateFramework;
engine: TemplateEngine; engine?: TemplateEngine;
ui: TemplateUI; ui: TemplateUI;
eslint: boolean; eslint: boolean;
customApiPath: string; customApiPath?: string;
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment