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

feat: generate fullstack app with fastapi or express

parent fdc2680a
No related branches found
No related tags found
No related merge requests found
......@@ -7,8 +7,10 @@ import { getOnline } from "./helpers/is-online";
import { isWriteable } from "./helpers/is-writeable";
import { makeDir } from "./helpers/make-dir";
import fs from "fs";
import terminalLink from "terminal-link";
import type { InstallTemplateArgs } from "./templates";
import { installPythonTemplate, installTemplate } from "./templates";
import { installTemplate } from "./templates";
export async function createApp({
template,
......@@ -18,9 +20,13 @@ export async function createApp({
appPath,
packageManager,
eslint,
customApiPath,
}: Omit<InstallTemplateArgs, "appName" | "root" | "isOnline"> & {
frontend,
}: Omit<
InstallTemplateArgs,
"appName" | "root" | "isOnline" | "customApiPath"
> & {
appPath: string;
frontend: boolean;
}): Promise<void> {
const root = path.resolve(appPath);
......@@ -47,30 +53,54 @@ export async function createApp({
console.log(`Creating a new LlamaIndex app in ${green(root)}.`);
console.log();
process.chdir(root);
const args = {
appName,
root,
template,
framework,
engine,
ui,
packageManager,
isOnline,
eslint,
};
if (framework === "fastapi") {
await installPythonTemplate({ appName, root, template, framework });
} else {
if (frontend) {
// install backend
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({
appName,
root,
template,
framework,
engine,
ui,
packageManager,
isOnline,
eslint,
customApiPath,
...args,
root: frontendRoot,
framework: "nextjs",
customApiPath: "http://localhost:8000/api/chat",
});
// 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)) {
console.log("Initialized a git repository.");
console.log();
}
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();
}
......@@ -11,7 +11,6 @@ import checkForUpdate from "update-check";
import { createApp } from "./create-app";
import { getPkgManager } from "./helpers/get-pkg-manager";
import { isFolderEmpty } from "./helpers/is-folder-empty";
import { isUrl } from "./helpers/is-url";
import { validateNpmName } from "./helpers/validate-pkg";
import packageJson from "./package.json";
......@@ -167,7 +166,7 @@ async function run(): Promise<void> {
engine: "simple",
ui: "html",
eslint: true,
customApiPath: "http://localhost:8000/api/chat",
frontend: false,
};
const getPrefOrDefault = (field: string) =>
preferences[field] ?? defaults[field];
......@@ -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 (ciInfo.isCI) {
program.ui = getPrefOrDefault("ui");
......@@ -253,15 +281,6 @@ async function run(): Promise<void> {
if (ciInfo.isCI) {
program.engine = getPrefOrDefault("engine");
} else {
const external =
program.framework === "nextjs"
? [
{
title: "External chat engine (e.g. FastAPI)",
value: "external",
},
]
: [];
const { engine } = await prompts(
{
type: "select",
......@@ -270,7 +289,6 @@ async function run(): Promise<void> {
choices: [
{ title: "SimpleChatEngine", value: "simple" },
{ title: "ContextChatEngine", value: "context" },
...external,
],
initial: 0,
},
......@@ -280,29 +298,6 @@ async function run(): Promise<void> {
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 (
......@@ -336,7 +331,7 @@ async function run(): Promise<void> {
appPath: resolvedProjectPath,
packageManager,
eslint: program.eslint,
customApiPath: program.customApiPath,
frontend: program.frontend,
});
conf.set("preferences", preferences);
}
......
......@@ -38,6 +38,7 @@
"@types/validate-npm-package-name": "3.0.0",
"@vercel/ncc": "0.34.0",
"async-retry": "1.3.1",
"async-sema": "3.0.1",
"ci-info": "watson/ci-info#f43f6a1cefff47fb361c88cf4b943fdbcaafe540",
"commander": "2.20.0",
"conf": "10.2.0",
......@@ -50,7 +51,7 @@
"tar": "6.1.15",
"update-check": "1.5.4",
"validate-npm-package-name": "3.0.0",
"async-sema": "3.0.1"
"terminal-link": "^3.0.0"
},
"engines": {
"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";
import { bold, cyan } from "picocolors";
import { version } from "../package.json";
import { InstallPythonTemplateArgs, InstallTemplateArgs } from "./types";
import { InstallTemplateArgs } from "./types";
/**
* Install a LlamaIndex internal template to a given `root` directory.
*/
export const installTemplate = async ({
const installTSTemplate = async ({
appName,
root,
packageManager,
......@@ -60,7 +60,7 @@ export const installTemplate = async ({
*/
let relativeEngineDestPath;
const compPath = path.join(__dirname, "components");
if (framework === "express" || framework === "nextjs") {
if (engine && (framework === "express" || framework === "nextjs")) {
console.log("\nUsing chat engine:", engine, "\n");
const enginePath = path.join(compPath, "engines", engine);
relativeEngineDestPath =
......@@ -101,7 +101,7 @@ export const installTemplate = async ({
llamaindex: version,
};
if (engine === "external" && customApiPath) {
if (framework === "nextjs" && customApiPath) {
console.log(
"\nUsing external API with custom API path:",
customApiPath,
......@@ -166,12 +166,11 @@ export const installTemplate = async ({
await install(packageManager, isOnline);
};
export const installPythonTemplate = async ({
appName,
const installPythonTemplate = async ({
root,
template,
framework,
}: InstallPythonTemplateArgs) => {
}: Pick<InstallTemplateArgs, "root" | "framework" | "template">) => {
console.log("\nInitializing Python project with template:", template, "\n");
const templatePath = path.join(__dirname, "types", template, framework);
await copy("**", root, {
......@@ -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";
......@@ -2,16 +2,9 @@ import { PackageManager } from "../helpers/get-pkg-manager";
export type TemplateType = "simple" | "streaming";
export type TemplateFramework = "nextjs" | "express" | "fastapi";
export type TemplateEngine = "simple" | "context" | "external";
export type TemplateEngine = "simple" | "context";
export type TemplateUI = "html" | "shadcn";
export interface InstallPythonTemplateArgs {
appName: string;
root: string;
template: TemplateType;
framework: TemplateFramework;
}
export interface InstallTemplateArgs {
appName: string;
root: string;
......@@ -19,8 +12,8 @@ export interface InstallTemplateArgs {
isOnline: boolean;
template: TemplateType;
framework: TemplateFramework;
engine: TemplateEngine;
engine?: TemplateEngine;
ui: TemplateUI;
eslint: boolean;
customApiPath: string;
customApiPath?: string;
}
......@@ -265,6 +265,9 @@ importers:
tar:
specifier: 6.1.15
version: 6.1.15
terminal-link:
specifier: ^3.0.0
version: 3.0.0
update-check:
specifier: 1.5.4
version: 1.5.4
......@@ -4765,6 +4768,13 @@ packages:
type-fest: 0.21.3
dev: true
 
/ansi-escapes@5.0.0:
resolution: {integrity: sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==}
engines: {node: '>=12'}
dependencies:
type-fest: 1.4.0
dev: true
/ansi-html-community@0.0.8:
resolution: {integrity: sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==}
engines: {'0': node >= 0.8.0}
......@@ -13677,6 +13687,14 @@ packages:
dependencies:
has-flag: 4.0.0
 
/supports-hyperlinks@2.3.0:
resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==}
engines: {node: '>=8'}
dependencies:
has-flag: 4.0.0
supports-color: 7.2.0
dev: true
/supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
......@@ -13740,6 +13758,14 @@ packages:
engines: {node: '>=8'}
dev: false
 
/terminal-link@3.0.0:
resolution: {integrity: sha512-flFL3m4wuixmf6IfhFJd1YPiLiMuxEc8uHRM1buzIeZPm22Au2pDqBJQgdo7n1WfPU1ONFGv7YDwpFBmHGF6lg==}
engines: {node: '>=12'}
dependencies:
ansi-escapes: 5.0.0
supports-hyperlinks: 2.3.0
dev: true
/terser-webpack-plugin@5.3.9(webpack@5.88.2):
resolution: {integrity: sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==}
engines: {node: '>= 10.13.0'}
......@@ -14229,6 +14255,11 @@ packages:
engines: {node: '>=8'}
dev: false
 
/type-fest@1.4.0:
resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==}
engines: {node: '>=10'}
dev: true
/type-fest@2.19.0:
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
engines: {node: '>=12.20'}
......
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