From 3cd8f9f5974005679447ecf5ac583af02f771698 Mon Sep 17 00:00:00 2001 From: Marcus Schiesser <mail@marcusschiesser.de> Date: Wed, 13 Mar 2024 15:53:33 +0700 Subject: [PATCH] refactor: move create-llama to own repo (#641) --- .changeset/hot-windows-watch.md | 5 - .github/workflows/e2e.yml | 68 -- .gitignore | 1 - .prettierignore | 1 - CONTRIBUTING.md | 3 +- package.json | 4 +- packages/create-llama/.eslintrc | 20 - packages/create-llama/CHANGELOG.md | 175 ---- packages/create-llama/LICENSE.md | 9 - packages/create-llama/README.md | 126 --- packages/create-llama/create-app.ts | 147 ---- packages/create-llama/e2e/basic.spec.ts | 145 ---- packages/create-llama/e2e/tsconfig.json | 16 - packages/create-llama/e2e/utils.ts | 181 ----- packages/create-llama/helpers/constant.ts | 6 - packages/create-llama/helpers/copy.ts | 50 -- packages/create-llama/helpers/devcontainer.ts | 61 -- packages/create-llama/helpers/dir.ts | 3 - .../create-llama/helpers/env-variables.ts | 242 ------ .../create-llama/helpers/get-pkg-manager.ts | 15 - packages/create-llama/helpers/git.ts | 58 -- packages/create-llama/helpers/index.ts | 192 ----- packages/create-llama/helpers/install.ts | 50 -- .../create-llama/helpers/is-folder-empty.ts | 62 -- packages/create-llama/helpers/is-online.ts | 40 - packages/create-llama/helpers/is-url.ts | 8 - packages/create-llama/helpers/is-writeable.ts | 10 - packages/create-llama/helpers/llama-pack.ts | 148 ---- packages/create-llama/helpers/make-dir.ts | 8 - packages/create-llama/helpers/poetry.ts | 36 - packages/create-llama/helpers/python.ts | 272 ------- packages/create-llama/helpers/repo.ts | 71 -- packages/create-llama/helpers/run-app.ts | 88 --- packages/create-llama/helpers/tools.ts | 71 -- packages/create-llama/helpers/types.ts | 52 -- packages/create-llama/helpers/typescript.ts | 227 ------ packages/create-llama/helpers/validate-pkg.ts | 20 - packages/create-llama/index.ts | 383 --------- packages/create-llama/package.json | 61 -- packages/create-llama/playwright.config.ts | 21 - packages/create-llama/questions.ts | 747 ------------------ packages/create-llama/templates/.gitignore | 3 - .../templates/README-fullstack.md | 18 - .../templates/components/data/101.pdf | Bin 47931 -> 0 bytes .../engines/python/agent/__init__.py | 28 - .../components/engines/python/agent/tools.py | 33 - .../engines/python/chat/__init__.py | 13 - .../components/loaders/python/file/loader.py | 7 - .../loaders/python/llama_parse/loader.py | 17 - .../components/loaders/python/web/loader.py | 13 - .../llamapack/README-template.md | 16 - .../sample-projects/llamapack/pyproject.toml | 17 - .../components/ui/html/chat/chat-avatar.tsx | 34 - .../components/ui/html/chat/chat-input.tsx | 43 - .../components/ui/html/chat/chat-item.tsx | 13 - .../components/ui/html/chat/chat-messages.tsx | 48 -- .../components/ui/html/chat/index.ts | 6 - .../vectordbs/python/mongo/__init__.py | 0 .../vectordbs/python/mongo/generate.py | 43 - .../vectordbs/python/mongo/index.py | 20 - .../vectordbs/python/none/__init__.py | 0 .../vectordbs/python/none/constants.py | 1 - .../vectordbs/python/none/generate.py | 32 - .../components/vectordbs/python/none/index.py | 23 - .../vectordbs/python/pg/__init__.py | 0 .../vectordbs/python/pg/constants.py | 2 - .../vectordbs/python/pg/generate.py | 35 - .../components/vectordbs/python/pg/index.py | 13 - .../components/vectordbs/python/pg/utils.py | 27 - .../vectordbs/python/pinecone/__init__.py | 0 .../vectordbs/python/pinecone/generate.py | 39 - .../vectordbs/python/pinecone/index.py | 20 - .../vectordbs/typescript/mongo/generate.mjs | 49 -- .../vectordbs/typescript/mongo/index.ts | 37 - .../vectordbs/typescript/mongo/shared.mjs | 27 - .../vectordbs/typescript/none/constants.mjs | 4 - .../vectordbs/typescript/none/generate.mjs | 53 -- .../vectordbs/typescript/none/index.ts | 44 -- .../vectordbs/typescript/pg/generate.mjs | 45 -- .../vectordbs/typescript/pg/index.ts | 39 - .../vectordbs/typescript/pg/shared.mjs | 24 - .../typescript/pinecone/generate.mjs | 35 - .../vectordbs/typescript/pinecone/index.ts | 29 - .../vectordbs/typescript/pinecone/shared.mjs | 22 - .../create-llama/templates/devcontainer.json | 35 - .../types/simple/express/README-template.md | 50 -- .../types/simple/express/eslintrc.json | 3 - .../templates/types/simple/express/gitignore | 3 - .../templates/types/simple/express/index.ts | 41 - .../types/simple/express/package.json | 27 - .../src/controllers/chat.controller.ts | 67 -- .../express/src/controllers/engine/index.ts | 7 - .../simple/express/src/routes/chat.route.ts | 8 - .../types/simple/express/tsconfig.json | 10 - .../types/simple/fastapi/README-template.md | 58 -- .../types/simple/fastapi/app/__init__.py | 0 .../types/simple/fastapi/app/api/__init__.py | 0 .../fastapi/app/api/routers/__init__.py | 0 .../simple/fastapi/app/api/routers/chat.py | 54 -- .../simple/fastapi/app/engine/__init__.py | 5 - .../types/simple/fastapi/app/settings.py | 41 - .../templates/types/simple/fastapi/gitignore | 3 - .../templates/types/simple/fastapi/main.py | 39 - .../types/simple/fastapi/pyproject.toml | 17 - .../types/simple/fastapi/tests/__init__.py | 0 .../streaming/express/README-template.md | 50 -- .../types/streaming/express/eslintrc.json | 3 - .../types/streaming/express/gitignore | 3 - .../types/streaming/express/index.ts | 41 - .../types/streaming/express/package.json | 28 - .../src/controllers/chat.controller.ts | 81 -- .../express/src/controllers/engine/index.ts | 7 - .../src/controllers/llamaindex-stream.ts | 68 -- .../express/src/routes/chat.route.ts | 8 - .../types/streaming/express/tsconfig.json | 10 - .../streaming/fastapi/README-template.md | 58 -- .../types/streaming/fastapi/app/__init__.py | 0 .../streaming/fastapi/app/api/__init__.py | 0 .../fastapi/app/api/routers/__init__.py | 0 .../streaming/fastapi/app/api/routers/chat.py | 59 -- .../streaming/fastapi/app/engine/__init__.py | 5 - .../types/streaming/fastapi/app/settings.py | 41 - .../types/streaming/fastapi/gitignore | 3 - .../templates/types/streaming/fastapi/main.py | 40 - .../types/streaming/fastapi/pyproject.toml | 17 - .../types/streaming/fastapi/tests/__init__.py | 0 .../types/streaming/nextjs/.env.example | 3 - .../types/streaming/nextjs/README-template.md | 30 - .../nextjs/app/api/chat/engine/index.ts | 7 - .../nextjs/app/api/chat/llamaindex-stream.ts | 68 -- .../streaming/nextjs/app/api/chat/route.ts | 84 -- .../nextjs/app/components/chat-section.tsx | 46 -- .../nextjs/app/components/header.tsx | 28 - .../nextjs/app/components/transform.ts | 19 - .../app/components/ui/README-template.md | 1 - .../nextjs/app/components/ui/button.tsx | 56 -- .../app/components/ui/chat/chat-actions.tsx | 28 - .../app/components/ui/chat/chat-avatar.tsx | 25 - .../app/components/ui/chat/chat-input.tsx | 84 -- .../app/components/ui/chat/chat-message.tsx | 64 -- .../app/components/ui/chat/chat-messages.tsx | 62 -- .../app/components/ui/chat/chat.interface.ts | 18 - .../app/components/ui/chat/codeblock.tsx | 139 ---- .../nextjs/app/components/ui/chat/index.ts | 5 - .../app/components/ui/chat/markdown.tsx | 59 -- .../ui/chat/use-copy-to-clipboard.tsx | 33 - .../app/components/ui/file-uploader.tsx | 105 --- .../nextjs/app/components/ui/input.tsx | 25 - .../nextjs/app/components/ui/lib/utils.ts | 6 - .../components/ui/upload-image-preview.tsx | 32 - .../types/streaming/nextjs/app/favicon.ico | Bin 15406 -> 0 bytes .../types/streaming/nextjs/app/globals.css | 94 --- .../types/streaming/nextjs/app/layout.tsx | 22 - .../types/streaming/nextjs/app/page.tsx | 11 - .../types/streaming/nextjs/eslintrc.json | 3 - .../types/streaming/nextjs/gitignore | 35 - .../types/streaming/nextjs/next-env.d.ts | 5 - .../types/streaming/nextjs/next.config.json | 7 - .../types/streaming/nextjs/next.config.mjs | 8 - .../types/streaming/nextjs/package.json | 43 - .../types/streaming/nextjs/postcss.config.js | 6 - .../types/streaming/nextjs/public/llama.png | Bin 36985 -> 0 bytes .../types/streaming/nextjs/tailwind.config.ts | 78 -- .../types/streaming/nextjs/tsconfig.json | 28 - .../types/streaming/nextjs/webpack.config.mjs | 10 - packages/create-llama/tsconfig.json | 24 - pnpm-lock.yaml | 435 ---------- tsconfig.json | 6 - 168 files changed, 2 insertions(+), 7705 deletions(-) delete mode 100644 .changeset/hot-windows-watch.md delete mode 100644 .github/workflows/e2e.yml delete mode 100644 packages/create-llama/.eslintrc delete mode 100644 packages/create-llama/CHANGELOG.md delete mode 100644 packages/create-llama/LICENSE.md delete mode 100644 packages/create-llama/README.md delete mode 100644 packages/create-llama/create-app.ts delete mode 100644 packages/create-llama/e2e/basic.spec.ts delete mode 100644 packages/create-llama/e2e/tsconfig.json delete mode 100644 packages/create-llama/e2e/utils.ts delete mode 100644 packages/create-llama/helpers/constant.ts delete mode 100644 packages/create-llama/helpers/copy.ts delete mode 100644 packages/create-llama/helpers/devcontainer.ts delete mode 100644 packages/create-llama/helpers/dir.ts delete mode 100644 packages/create-llama/helpers/env-variables.ts delete mode 100644 packages/create-llama/helpers/get-pkg-manager.ts delete mode 100644 packages/create-llama/helpers/git.ts delete mode 100644 packages/create-llama/helpers/index.ts delete mode 100644 packages/create-llama/helpers/install.ts delete mode 100644 packages/create-llama/helpers/is-folder-empty.ts delete mode 100644 packages/create-llama/helpers/is-online.ts delete mode 100644 packages/create-llama/helpers/is-url.ts delete mode 100644 packages/create-llama/helpers/is-writeable.ts delete mode 100644 packages/create-llama/helpers/llama-pack.ts delete mode 100644 packages/create-llama/helpers/make-dir.ts delete mode 100644 packages/create-llama/helpers/poetry.ts delete mode 100644 packages/create-llama/helpers/python.ts delete mode 100644 packages/create-llama/helpers/repo.ts delete mode 100644 packages/create-llama/helpers/run-app.ts delete mode 100644 packages/create-llama/helpers/tools.ts delete mode 100644 packages/create-llama/helpers/types.ts delete mode 100644 packages/create-llama/helpers/typescript.ts delete mode 100644 packages/create-llama/helpers/validate-pkg.ts delete mode 100644 packages/create-llama/index.ts delete mode 100644 packages/create-llama/package.json delete mode 100644 packages/create-llama/playwright.config.ts delete mode 100644 packages/create-llama/questions.ts delete mode 100644 packages/create-llama/templates/.gitignore delete mode 100644 packages/create-llama/templates/README-fullstack.md delete mode 100644 packages/create-llama/templates/components/data/101.pdf delete mode 100644 packages/create-llama/templates/components/engines/python/agent/__init__.py delete mode 100644 packages/create-llama/templates/components/engines/python/agent/tools.py delete mode 100644 packages/create-llama/templates/components/engines/python/chat/__init__.py delete mode 100644 packages/create-llama/templates/components/loaders/python/file/loader.py delete mode 100644 packages/create-llama/templates/components/loaders/python/llama_parse/loader.py delete mode 100644 packages/create-llama/templates/components/loaders/python/web/loader.py delete mode 100644 packages/create-llama/templates/components/sample-projects/llamapack/README-template.md delete mode 100644 packages/create-llama/templates/components/sample-projects/llamapack/pyproject.toml delete mode 100644 packages/create-llama/templates/components/ui/html/chat/chat-avatar.tsx delete mode 100644 packages/create-llama/templates/components/ui/html/chat/chat-input.tsx delete mode 100644 packages/create-llama/templates/components/ui/html/chat/chat-item.tsx delete mode 100644 packages/create-llama/templates/components/ui/html/chat/chat-messages.tsx delete mode 100644 packages/create-llama/templates/components/ui/html/chat/index.ts delete mode 100644 packages/create-llama/templates/components/vectordbs/python/mongo/__init__.py delete mode 100644 packages/create-llama/templates/components/vectordbs/python/mongo/generate.py delete mode 100644 packages/create-llama/templates/components/vectordbs/python/mongo/index.py delete mode 100644 packages/create-llama/templates/components/vectordbs/python/none/__init__.py delete mode 100644 packages/create-llama/templates/components/vectordbs/python/none/constants.py delete mode 100644 packages/create-llama/templates/components/vectordbs/python/none/generate.py delete mode 100644 packages/create-llama/templates/components/vectordbs/python/none/index.py delete mode 100644 packages/create-llama/templates/components/vectordbs/python/pg/__init__.py delete mode 100644 packages/create-llama/templates/components/vectordbs/python/pg/constants.py delete mode 100644 packages/create-llama/templates/components/vectordbs/python/pg/generate.py delete mode 100644 packages/create-llama/templates/components/vectordbs/python/pg/index.py delete mode 100644 packages/create-llama/templates/components/vectordbs/python/pg/utils.py delete mode 100644 packages/create-llama/templates/components/vectordbs/python/pinecone/__init__.py delete mode 100644 packages/create-llama/templates/components/vectordbs/python/pinecone/generate.py delete mode 100644 packages/create-llama/templates/components/vectordbs/python/pinecone/index.py delete mode 100644 packages/create-llama/templates/components/vectordbs/typescript/mongo/generate.mjs delete mode 100644 packages/create-llama/templates/components/vectordbs/typescript/mongo/index.ts delete mode 100644 packages/create-llama/templates/components/vectordbs/typescript/mongo/shared.mjs delete mode 100644 packages/create-llama/templates/components/vectordbs/typescript/none/constants.mjs delete mode 100644 packages/create-llama/templates/components/vectordbs/typescript/none/generate.mjs delete mode 100644 packages/create-llama/templates/components/vectordbs/typescript/none/index.ts delete mode 100644 packages/create-llama/templates/components/vectordbs/typescript/pg/generate.mjs delete mode 100644 packages/create-llama/templates/components/vectordbs/typescript/pg/index.ts delete mode 100644 packages/create-llama/templates/components/vectordbs/typescript/pg/shared.mjs delete mode 100644 packages/create-llama/templates/components/vectordbs/typescript/pinecone/generate.mjs delete mode 100644 packages/create-llama/templates/components/vectordbs/typescript/pinecone/index.ts delete mode 100644 packages/create-llama/templates/components/vectordbs/typescript/pinecone/shared.mjs delete mode 100644 packages/create-llama/templates/devcontainer.json delete mode 100644 packages/create-llama/templates/types/simple/express/README-template.md delete mode 100644 packages/create-llama/templates/types/simple/express/eslintrc.json delete mode 100644 packages/create-llama/templates/types/simple/express/gitignore delete mode 100644 packages/create-llama/templates/types/simple/express/index.ts delete mode 100644 packages/create-llama/templates/types/simple/express/package.json delete mode 100644 packages/create-llama/templates/types/simple/express/src/controllers/chat.controller.ts delete mode 100644 packages/create-llama/templates/types/simple/express/src/controllers/engine/index.ts delete mode 100644 packages/create-llama/templates/types/simple/express/src/routes/chat.route.ts delete mode 100644 packages/create-llama/templates/types/simple/express/tsconfig.json delete mode 100644 packages/create-llama/templates/types/simple/fastapi/README-template.md delete mode 100644 packages/create-llama/templates/types/simple/fastapi/app/__init__.py delete mode 100644 packages/create-llama/templates/types/simple/fastapi/app/api/__init__.py delete mode 100644 packages/create-llama/templates/types/simple/fastapi/app/api/routers/__init__.py delete mode 100644 packages/create-llama/templates/types/simple/fastapi/app/api/routers/chat.py delete mode 100644 packages/create-llama/templates/types/simple/fastapi/app/engine/__init__.py delete mode 100644 packages/create-llama/templates/types/simple/fastapi/app/settings.py delete mode 100644 packages/create-llama/templates/types/simple/fastapi/gitignore delete mode 100644 packages/create-llama/templates/types/simple/fastapi/main.py delete mode 100644 packages/create-llama/templates/types/simple/fastapi/pyproject.toml delete mode 100644 packages/create-llama/templates/types/simple/fastapi/tests/__init__.py delete mode 100644 packages/create-llama/templates/types/streaming/express/README-template.md delete mode 100644 packages/create-llama/templates/types/streaming/express/eslintrc.json delete mode 100644 packages/create-llama/templates/types/streaming/express/gitignore delete mode 100644 packages/create-llama/templates/types/streaming/express/index.ts delete mode 100644 packages/create-llama/templates/types/streaming/express/package.json delete mode 100644 packages/create-llama/templates/types/streaming/express/src/controllers/chat.controller.ts delete mode 100644 packages/create-llama/templates/types/streaming/express/src/controllers/engine/index.ts delete mode 100644 packages/create-llama/templates/types/streaming/express/src/controllers/llamaindex-stream.ts delete mode 100644 packages/create-llama/templates/types/streaming/express/src/routes/chat.route.ts delete mode 100644 packages/create-llama/templates/types/streaming/express/tsconfig.json delete mode 100644 packages/create-llama/templates/types/streaming/fastapi/README-template.md delete mode 100644 packages/create-llama/templates/types/streaming/fastapi/app/__init__.py delete mode 100644 packages/create-llama/templates/types/streaming/fastapi/app/api/__init__.py delete mode 100644 packages/create-llama/templates/types/streaming/fastapi/app/api/routers/__init__.py delete mode 100644 packages/create-llama/templates/types/streaming/fastapi/app/api/routers/chat.py delete mode 100644 packages/create-llama/templates/types/streaming/fastapi/app/engine/__init__.py delete mode 100644 packages/create-llama/templates/types/streaming/fastapi/app/settings.py delete mode 100644 packages/create-llama/templates/types/streaming/fastapi/gitignore delete mode 100644 packages/create-llama/templates/types/streaming/fastapi/main.py delete mode 100644 packages/create-llama/templates/types/streaming/fastapi/pyproject.toml delete mode 100644 packages/create-llama/templates/types/streaming/fastapi/tests/__init__.py delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/.env.example delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/README-template.md delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/api/chat/engine/index.ts delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/api/chat/llamaindex-stream.ts delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/api/chat/route.ts delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/chat-section.tsx delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/header.tsx delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/transform.ts delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/ui/README-template.md delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/ui/button.tsx delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-actions.tsx delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-avatar.tsx delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-input.tsx delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-message.tsx delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-messages.tsx delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat.interface.ts delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/codeblock.tsx delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/index.ts delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/markdown.tsx delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/use-copy-to-clipboard.tsx delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/ui/file-uploader.tsx delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/ui/input.tsx delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/ui/lib/utils.ts delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/components/ui/upload-image-preview.tsx delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/favicon.ico delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/globals.css delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/layout.tsx delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/app/page.tsx delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/eslintrc.json delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/gitignore delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/next-env.d.ts delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/next.config.json delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/next.config.mjs delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/package.json delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/postcss.config.js delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/public/llama.png delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/tailwind.config.ts delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/tsconfig.json delete mode 100644 packages/create-llama/templates/types/streaming/nextjs/webpack.config.mjs delete mode 100644 packages/create-llama/tsconfig.json diff --git a/.changeset/hot-windows-watch.md b/.changeset/hot-windows-watch.md deleted file mode 100644 index 0020daf70..000000000 --- a/.changeset/hot-windows-watch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"create-llama": patch ---- - -fix missing .env value, improve docs and error message diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml deleted file mode 100644 index 70cda2ca6..000000000 --- a/.github/workflows/e2e.yml +++ /dev/null @@ -1,68 +0,0 @@ -name: E2E Tests -on: - push: - branches: [main] - pull_request: - paths: - - "packages/create-llama/**" - - ".github/workflows/e2e.yml" - branches: [main] - -env: - POETRY_VERSION: "1.6.1" - -jobs: - e2e: - name: create-llama - timeout-minutes: 60 - strategy: - fail-fast: true - matrix: - node-version: [18, 20] - python-version: ["3.11"] - os: [macos-latest, windows-latest] - defaults: - run: - shell: bash - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - name: Set up python ${{ matrix.python-version }} - uses: actions/setup-python@v4 - with: - python-version: ${{ matrix.python-version }} - - name: Install Poetry - uses: snok/install-poetry@v1 - with: - version: ${{ env.POETRY_VERSION }} - - uses: pnpm/action-setup@v2 - - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: "pnpm" - - name: Install dependencies - run: pnpm install - - name: Install Playwright Browsers - run: pnpm exec playwright install --with-deps - working-directory: ./packages/create-llama - - name: Build create-llama - run: pnpm run build - working-directory: ./packages/create-llama - - name: Pack - run: pnpm pack --pack-destination ./output - working-directory: ./packages/create-llama - - name: Extract Pack - run: tar -xvzf ./output/*.tgz -C ./output - working-directory: ./packages/create-llama - - name: Run Playwright tests - run: pnpm exec playwright test - env: - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} - working-directory: ./packages/create-llama - - uses: actions/upload-artifact@v3 - if: always() - with: - name: playwright-report - path: ./packages/create-llama/playwright-report/ - retention-days: 30 diff --git a/.gitignore b/.gitignore index 8d11dc468..8f37f7c04 100644 --- a/.gitignore +++ b/.gitignore @@ -45,7 +45,6 @@ playwright-report/ blob-report/ playwright/.cache/ .tsbuildinfo -packages/create-llama/e2e/cache # intellij **/.idea diff --git a/.prettierignore b/.prettierignore index d19028fa8..5cbece915 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,4 +4,3 @@ pnpm-lock.yaml lib/ dist/ .docusaurus/ -packages/create-llama/e2e/cache/ \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b4797aec..8632eb698 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -84,8 +84,7 @@ Any changes you make should be reflected in the browser. If you need to regenera To publish a new version of the library, run ```shell -pnpm new-llamaindex -pnpm new-create-llama +pnpm new-version pnpm release git push # push to the main branch git push --tags diff --git a/package.json b/package.json index bc5660b56..dc6ca9cb0 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,7 @@ "test": "turbo run test", "type-check": "tsc -b --diagnostics", "release": "pnpm run build:release && changeset publish", - "new-llamaindex": "pnpm run build:release && changeset version --ignore create-llama", - "new-create-llama": "pnpm run build:release && changeset version --ignore llamaindex --ignore @llamaindex/core-test --ignore @llamaindex/experimental", - "new-experimental": "pnpm run build:release && changeset version --ignore create-llama" + "new-version": "pnpm run build:release && changeset version" }, "devDependencies": { "@changesets/cli": "^2.27.1", diff --git a/packages/create-llama/.eslintrc b/packages/create-llama/.eslintrc deleted file mode 100644 index fc6949f08..000000000 --- a/packages/create-llama/.eslintrc +++ /dev/null @@ -1,20 +0,0 @@ -{ - "root": false, - "rules": { - "turbo/no-undeclared-env-vars": [ - "error", - { - "allowList": [ - "OPENAI_API_KEY", - "LLAMA_CLOUD_API_KEY", - "npm_config_user_agent", - "http_proxy", - "https_proxy", - "MODEL", - "NEXT_PUBLIC_CHAT_API", - "NEXT_PUBLIC_MODEL" - ] - } - ] - } -} diff --git a/packages/create-llama/CHANGELOG.md b/packages/create-llama/CHANGELOG.md deleted file mode 100644 index f2b4f858f..000000000 --- a/packages/create-llama/CHANGELOG.md +++ /dev/null @@ -1,175 +0,0 @@ -# create-llama - -## 0.0.28 - -### Patch Changes - -- 89a49f4: Add more config variables to .env file -- fdf48dd: Add "Start in VSCode" option to postInstallAction -- fdf48dd: Add devcontainers to generated code - -## 0.0.27 - -### Patch Changes - -- 2d29350: Add LlamaParse option when selecting a pdf file or a folder (FastAPI only) -- b354f23: Add embedding model option to create-llama (FastAPI only) - -## 0.0.26 - -### Patch Changes - -- 09d532e: feat: generate llama pack project from llama index -- cfdd6db: feat: add pinecone support to create llama -- ef25d69: upgrade llama-index package to version v0.10.7 for create-llama app -- 50dfd7b: update fastapi for CVE-2024-24762 - -## 0.0.25 - -### Patch Changes - -- d06a85b: Add option to create an agent by selecting tools (Google, Wikipedia) -- 7b7329b: Added latest turbo models for GPT-3.5 and GPT 4 - -## 0.0.24 - -### Patch Changes - -- ba95ca3: Use condense plus context chat engine for FastAPI as default - -## 0.0.23 - -### Patch Changes - -- c680af6: Fixed issues with locating templates path - -## 0.0.22 - -### Patch Changes - -- 6dd401e: Add an option to provide an URL and chat with the website data (FastAPI only) -- e9b87ef: Select a folder as data source and support more file types (.pdf, .doc, .docx, .xls, .xlsx, .csv) - -## 0.0.20 - -### Patch Changes - -- 27d55fd: Add an option to provide an URL and chat with the website data - -## 0.0.19 - -### Patch Changes - -- 3a29a80: Add node_modules to gitignore in Express backends -- fe03aaa: feat: generate llama pack example - -## 0.0.18 - -### Patch Changes - -- 88d3b41: fix packaging - -## 0.0.17 - -### Patch Changes - -- fa17f7e: Add an option that allows the user to run the generated app -- 9e5d8e1: Add an option to select a local PDF file as data source - -## 0.0.16 - -### Patch Changes - -- a73942d: Fix: Bundle mongo dependency with NextJS -- 9492cc6: Feat: Added option to automatically install dependencies (for Python and TS) -- f74dea5: Feat: Show images in chat messages using GPT4 Vision (Express and NextJS only) - -## 0.0.15 - -### Patch Changes - -- 8e124e5: feat: support showing image on chat message - -## 0.0.14 - -### Patch Changes - -- 2e6b36e: fix: re-organize file structure -- 2b356c8: fix: relative path incorrect - -## 0.0.13 - -### Patch Changes - -- Added PostgreSQL vector store (for Typescript and Python) -- Improved async handling in FastAPI - -## 0.0.12 - -### Patch Changes - -- 9c5e22a: Added cross-env so frontends with Express/FastAPI backends are working under Windows -- 5ab65eb: Bring Python templates with TS templates to feature parity -- 9c5e22a: Added vector DB selector to create-llama (starting with MongoDB support) - -## 0.0.11 - -### Patch Changes - -- 2aeb341: - Added option to create a new project based on community templates - - Added OpenAI model selector for NextJS projects - - Added GPT4 Vision support (and file upload) - -## 0.0.10 - -### Patch Changes - -- Bugfixes (thanks @marcusschiesser) - -## 0.0.9 - -### Patch Changes - -- acfe232: Deployment fixes (thanks @seldo) - -## 0.0.8 - -### Patch Changes - -- 8cdb07f: Fix Next deployment (thanks @seldo and @marcusschiesser) - -## 0.0.7 - -### Patch Changes - -- 9f9f293: Added more to README and made it easier to switch models (thanks @seldo) - -## 0.0.6 - -### Patch Changes - -- 4431ec7: Label bug fix (thanks @marcusschiesser) - -## 0.0.5 - -### Patch Changes - -- 25257f4: Fix issue where it doesn't find OpenAI Key when running npm run generate (#182) (thanks @RayFernando1337) - -## 0.0.4 - -### Patch Changes - -- 031e926: Update create-llama readme (thanks @logan-markewich) - -## 0.0.3 - -### Patch Changes - -- 91b42a3: change version (thanks @marcusschiesser) - -## 0.0.2 - -### Patch Changes - -- e2a6805: Hello Create Llama (thanks @marcusschiesser) diff --git a/packages/create-llama/LICENSE.md b/packages/create-llama/LICENSE.md deleted file mode 100644 index c16e650c0..000000000 --- a/packages/create-llama/LICENSE.md +++ /dev/null @@ -1,9 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2023 LlamaIndex, Vercel, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/create-llama/README.md b/packages/create-llama/README.md deleted file mode 100644 index 1f6ba4777..000000000 --- a/packages/create-llama/README.md +++ /dev/null @@ -1,126 +0,0 @@ -# Create LlamaIndex App - -The easiest way to get started with [LlamaIndex](https://www.llamaindex.ai/) is by using `create-llama`. This CLI tool enables you to quickly start building a new LlamaIndex application, with everything set up for you. - -Just run - -```bash -npx create-llama@latest -``` - -to get started, or see below for more options. Once your app is generated, run - -```bash -npm run dev -``` - -to start the development server. You can then visit [http://localhost:3000](http://localhost:3000) to see your app. - -## What you'll get - -- A Next.js-powered front-end. The app is set up as a chat interface that can answer questions about your data (see below) - - You can style it with HTML and CSS, or you can optionally use components from [shadcn/ui](https://ui.shadcn.com/) -- Your choice of 3 back-ends: - - **Next.js**: if you select this option, you’ll have a full stack Next.js application that you can deploy to a host like [Vercel](https://vercel.com/) in just a few clicks. This uses [LlamaIndex.TS](https://www.npmjs.com/package/llamaindex), our TypeScript library. - - **Express**: if you want a more traditional Node.js application you can generate an Express backend. This also uses LlamaIndex.TS. - - **Python FastAPI**: if you select this option you’ll get a backend powered by the [llama-index python package](https://pypi.org/project/llama-index/), which you can deploy to a service like Render or fly.io. -- The back-end has a single endpoint that allows you to send the state of your chat and receive additional responses -- You can choose whether you want a streaming or non-streaming back-end (if you're not sure, we recommend streaming) -- You can choose whether you want to use `ContextChatEngine` or `SimpleChatEngine` - - `SimpleChatEngine` will just talk to the LLM directly without using your data - - `ContextChatEngine` will use your data to answer questions (see below). -- The app uses OpenAI by default, so you'll need an OpenAI API key, or you can customize it to use any of the dozens of LLMs we support. - -## Using your data - -If you've enabled `ContextChatEngine`, you can supply your own data and the app will index it and answer questions. Your generated app will have a folder called `data`: - -- With the Next.js backend this is `./data` -- With the Express or Python backend this is in `./backend/data` - -The app will ingest any supported files you put in this directory. Your Next.js and Express apps use LlamaIndex.TS so they will be able to ingest any PDF, text, CSV, Markdown, Word and HTML files. The Python backend can read even more types, including video and audio files. - -Before you can use your data, you need to index it. If you're using the Next.js or Express apps, run: - -```bash -npm run generate -``` - -Then re-start your app. Remember you'll need to re-run `generate` if you add new files to your `data` folder. If you're using the Python backend, you can trigger indexing of your data by deleting the `./storage` folder and re-starting the app. - -## Don't want a front-end? - -It's optional! If you've selected the Python or Express back-ends, just delete the `frontend` folder and you'll get an API without any front-end code. - -## Customizing the LLM - -By default the app will use OpenAI's gpt-3.5-turbo model. If you want to use GPT-4, you can modify this by editing a file: - -- In the Next.js backend, edit `./app/api/chat/route.ts` and replace `gpt-3.5-turbo` with `gpt-4` -- In the Express backend, edit `./backend/src/controllers/chat.controller.ts` and likewise replace `gpt-3.5-turbo` with `gpt-4` -- In the Python backend, edit `./backend/app/utils/index.py` and once again replace `gpt-3.5-turbo` with `gpt-4` - -You can also replace OpenAI with one of our [dozens of other supported LLMs](https://docs.llamaindex.ai/en/stable/module_guides/models/llms/modules.html). - -## Example - -The simplest thing to do is run `create-llama` in interactive mode: - -```bash -npx create-llama@latest -# or -npm create llama@latest -# or -yarn create llama -# or -pnpm create llama@latest -``` - -You will be asked for the name of your project, along with other configuration options, something like this: - -```bash ->> npm create llama@latest -Need to install the following packages: - create-llama@latest -Ok to proceed? (y) y -✔ What is your project named? … my-app -✔ Which template would you like to use? › Chat with streaming -✔ Which framework would you like to use? › NextJS -✔ Which UI would you like to use? › Just HTML -✔ Which chat engine would you like to use? › ContextChatEngine -✔ Please provide your OpenAI API key (leave blank to skip): … -✔ Would you like to use ESLint? … No / Yes -Creating a new LlamaIndex app in /home/my-app. -``` - -### Running non-interactively - -You can also pass command line arguments to set up a new project -non-interactively. See `create-llama --help`: - -```bash -create-llama <project-directory> [options] - -Options: - -V, --version output the version number - - --use-npm - - Explicitly tell the CLI to bootstrap the app using npm - - --use-pnpm - - Explicitly tell the CLI to bootstrap the app using pnpm - - --use-yarn - - Explicitly tell the CLI to bootstrap the app using Yarn - -``` - -## LlamaIndex Documentation - -- [TS/JS docs](https://ts.llamaindex.ai/) -- [Python docs](https://docs.llamaindex.ai/en/stable/) - -Inspired by and adapted from [create-next-app](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) diff --git a/packages/create-llama/create-app.ts b/packages/create-llama/create-app.ts deleted file mode 100644 index 8d6ce9c50..000000000 --- a/packages/create-llama/create-app.ts +++ /dev/null @@ -1,147 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import path from "path"; -import { green, yellow } from "picocolors"; -import { tryGitInit } from "./helpers/git"; -import { isFolderEmpty } from "./helpers/is-folder-empty"; -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 "./helpers"; -import { installTemplate } from "./helpers"; -import { writeDevcontainer } from "./helpers/devcontainer"; -import { templatesDir } from "./helpers/dir"; -import { toolsRequireConfig } from "./helpers/tools"; - -export type InstallAppArgs = Omit< - InstallTemplateArgs, - "appName" | "root" | "isOnline" | "customApiPath" -> & { - appPath: string; - frontend: boolean; -}; - -export async function createApp({ - template, - framework, - engine, - ui, - appPath, - packageManager, - eslint, - frontend, - openAiKey, - llamaCloudKey, - model, - embeddingModel, - communityProjectPath, - llamapack, - vectorDb, - externalPort, - postInstallAction, - dataSource, - tools, -}: InstallAppArgs): Promise<void> { - const root = path.resolve(appPath); - - if (!(await isWriteable(path.dirname(root)))) { - console.error( - "The application path is not writable, please check folder permissions and try again.", - ); - console.error( - "It is likely you do not have write permissions for this folder.", - ); - process.exit(1); - } - - const appName = path.basename(root); - - await makeDir(root); - if (!isFolderEmpty(root, appName)) { - process.exit(1); - } - - const useYarn = packageManager === "yarn"; - const isOnline = !useYarn || (await getOnline()); - - console.log(`Creating a new LlamaIndex app in ${green(root)}.`); - console.log(); - - const args = { - appName, - root, - template, - framework, - engine, - ui, - packageManager, - isOnline, - eslint, - openAiKey, - llamaCloudKey, - model, - embeddingModel, - communityProjectPath, - llamapack, - vectorDb, - externalPort, - postInstallAction, - dataSource, - tools, - }; - - if (frontend) { - // install backend - const backendRoot = path.join(root, "backend"); - await makeDir(backendRoot); - await installTemplate({ ...args, root: backendRoot, backend: true }); - // install frontend - const frontendRoot = path.join(root, "frontend"); - await makeDir(frontendRoot); - await installTemplate({ - ...args, - root: frontendRoot, - framework: "nextjs", - customApiPath: `http://localhost:${externalPort ?? 8000}/api/chat`, - backend: false, - }); - // copy readme for fullstack - await fs.promises.copyFile( - path.join(templatesDir, "README-fullstack.md"), - path.join(root, "README.md"), - ); - } else { - await installTemplate({ ...args, backend: true }); - } - - await writeDevcontainer(root, templatesDir, framework, frontend); - - process.chdir(root); - if (tryGitInit(root)) { - console.log("Initialized a git repository."); - console.log(); - } - - if (toolsRequireConfig(tools)) { - console.log( - yellow( - `You have selected tools that require configuration. Please configure them in the ${terminalLink( - "tools_config.json", - `file://${root}/tools_config.json`, - )} file.`, - ), - ); - } - console.log(""); - console.log(`${green("Success!")} Created ${appName} at ${appPath}`); - - console.log( - `Now have a look at the ${terminalLink( - "README.md", - `file://${root}/README.md`, - )} and learn how to get started.`, - ); - console.log(); -} diff --git a/packages/create-llama/e2e/basic.spec.ts b/packages/create-llama/e2e/basic.spec.ts deleted file mode 100644 index 93e3edfb9..000000000 --- a/packages/create-llama/e2e/basic.spec.ts +++ /dev/null @@ -1,145 +0,0 @@ -/* eslint-disable turbo/no-undeclared-env-vars */ -import { expect, test } from "@playwright/test"; -import { ChildProcess } from "child_process"; -import fs from "fs"; -import path from "path"; -import type { - TemplateEngine, - TemplateFramework, - TemplatePostInstallAction, - TemplateType, - TemplateUI, -} from "../helpers"; -import { createTestDir, runCreateLlama, type AppType } from "./utils"; - -const templateTypes: TemplateType[] = ["streaming", "simple"]; -const templateFrameworks: TemplateFramework[] = [ - "nextjs", - "express", - "fastapi", -]; -const templateEngines: TemplateEngine[] = ["simple", "context"]; -const templateUIs: TemplateUI[] = ["shadcn", "html"]; -const templatePostInstallActions: TemplatePostInstallAction[] = [ - "none", - "runApp", -]; - -for (const templateType of templateTypes) { - for (const templateFramework of templateFrameworks) { - for (const templateEngine of templateEngines) { - for (const templateUI of templateUIs) { - for (const templatePostInstallAction of templatePostInstallActions) { - if (templateFramework === "nextjs" && templateType === "simple") { - // nextjs doesn't support simple templates - skip tests - continue; - } - const appType: AppType = - templateFramework === "express" || templateFramework === "fastapi" - ? templateType === "simple" - ? "--no-frontend" // simple templates don't have frontends - : "--frontend" - : ""; - if (appType === "--no-frontend" && templateUI !== "html") { - // if there's no frontend, don't iterate over UIs - continue; - } - test.describe(`try create-llama ${templateType} ${templateFramework} ${templateEngine} ${templateUI} ${appType} ${templatePostInstallAction}`, async () => { - let port: number; - let externalPort: number; - let cwd: string; - let name: string; - let appProcess: ChildProcess; - // Only test without using vector db for now - const vectorDb = "none"; - - test.beforeAll(async () => { - port = Math.floor(Math.random() * 10000) + 10000; - externalPort = port + 1; - cwd = await createTestDir(); - const result = await runCreateLlama( - cwd, - templateType, - templateFramework, - templateEngine, - templateUI, - vectorDb, - appType, - port, - externalPort, - templatePostInstallAction, - ); - name = result.projectName; - appProcess = result.appProcess; - }); - - test("App folder should exist", async () => { - const dirExists = fs.existsSync(path.join(cwd, name)); - expect(dirExists).toBeTruthy(); - }); - test("Frontend should have a title", async ({ page }) => { - test.skip(templatePostInstallAction !== "runApp"); - test.skip(appType === "--no-frontend"); - await page.goto(`http://localhost:${port}`); - await expect(page.getByText("Built by LlamaIndex")).toBeVisible(); - }); - - test("Frontend should be able to submit a message and receive a response", async ({ - page, - }) => { - test.skip(templatePostInstallAction !== "runApp"); - test.skip(appType === "--no-frontend"); - await page.goto(`http://localhost:${port}`); - await page.fill("form input", "hello"); - const [response] = await Promise.all([ - page.waitForResponse( - (res) => { - return ( - res.url().includes("/api/chat") && res.status() === 200 - ); - }, - { - timeout: 1000 * 60, - }, - ), - page.click("form button[type=submit]"), - ]); - const text = await response.text(); - console.log("AI response when submitting message: ", text); - expect(response.ok()).toBeTruthy(); - }); - - test("Backend should response when calling API", async ({ - request, - }) => { - test.skip(templatePostInstallAction !== "runApp"); - test.skip(appType !== "--no-frontend"); - const backendPort = appType === "" ? port : externalPort; - const response = await request.post( - `http://localhost:${backendPort}/api/chat`, - { - data: { - messages: [ - { - role: "user", - content: "Hello", - }, - ], - }, - }, - ); - const text = await response.text(); - console.log("AI response when calling API: ", text); - expect(response.ok()).toBeTruthy(); - }); - - // clean processes - test.afterAll(async () => { - appProcess?.kill(); - }); - }); - } - } - } - } -} diff --git a/packages/create-llama/e2e/tsconfig.json b/packages/create-llama/e2e/tsconfig.json deleted file mode 100644 index d2ea7ec52..000000000 --- a/packages/create-llama/e2e/tsconfig.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "compilerOptions": { - "target": "es2019", - "module": "esnext", - "moduleResolution": "node", - "strict": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "declaration": false, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "incremental": true, - "tsBuildInfoFile": "./lib/.tsbuildinfo" - }, - "include": ["./**/*.ts"] -} diff --git a/packages/create-llama/e2e/utils.ts b/packages/create-llama/e2e/utils.ts deleted file mode 100644 index 03b69d07e..000000000 --- a/packages/create-llama/e2e/utils.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { ChildProcess, exec } from "child_process"; -import crypto from "node:crypto"; -import { mkdir } from "node:fs/promises"; -import * as path from "path"; -import waitPort from "wait-port"; -import { - TemplateEngine, - TemplateFramework, - TemplatePostInstallAction, - TemplateType, - TemplateUI, - TemplateVectorDB, -} from "../helpers"; - -export type AppType = "--frontend" | "--no-frontend" | ""; -const MODEL = "gpt-3.5-turbo"; -const EMBEDDING_MODEL = "text-embedding-ada-002"; -export type CreateLlamaResult = { - projectName: string; - appProcess: ChildProcess; -}; - -// eslint-disable-next-line max-params -export async function checkAppHasStarted( - frontend: boolean, - framework: TemplateFramework, - port: number, - externalPort: number, - timeout: number, -) { - if (frontend) { - await Promise.all([ - waitPort({ - host: "localhost", - port: port, - timeout, - }), - waitPort({ - host: "localhost", - port: externalPort, - timeout, - }), - ]).catch((err) => { - console.error(err); - throw err; - }); - } else { - let wPort: number; - if (framework === "nextjs") { - wPort = port; - } else { - wPort = externalPort; - } - await waitPort({ - host: "localhost", - port: wPort, - timeout, - }).catch((err) => { - console.error(err); - throw err; - }); - } -} - -// eslint-disable-next-line max-params -export async function runCreateLlama( - cwd: string, - templateType: TemplateType, - templateFramework: TemplateFramework, - templateEngine: TemplateEngine, - templateUI: TemplateUI, - vectorDb: TemplateVectorDB, - appType: AppType, - port: number, - externalPort: number, - postInstallAction: TemplatePostInstallAction, -): Promise<CreateLlamaResult> { - const createLlama = path.join( - __dirname, - "..", - "output", - "package", - "dist", - "index.js", - ); - - const name = [ - templateType, - templateFramework, - templateEngine, - templateUI, - appType, - ].join("-"); - const command = [ - "node", - createLlama, - name, - "--template", - templateType, - "--framework", - templateFramework, - "--engine", - templateEngine, - "--ui", - templateUI, - "--vector-db", - vectorDb, - "--model", - MODEL, - "--embedding-model", - EMBEDDING_MODEL, - "--open-ai-key", - process.env.OPENAI_API_KEY || "testKey", - appType, - "--eslint", - "--use-npm", - "--port", - port, - "--external-port", - externalPort, - "--post-install-action", - postInstallAction, - "--tools", - "none", - "--no-llama-parse", - ].join(" "); - console.log(`running command '${command}' in ${cwd}`); - const appProcess = exec(command, { - cwd, - env: { - ...process.env, - }, - }); - appProcess.stderr?.on("data", (data) => { - console.log(data.toString()); - }); - appProcess.on("exit", (code) => { - if (code !== 0 && code !== null) { - throw new Error(`create-llama command was failed!`); - } - }); - - // Wait for app to start - if (postInstallAction === "runApp") { - await checkAppHasStarted( - appType === "--frontend", - templateFramework, - port, - externalPort, - 1000 * 60 * 5, - ); - } else { - // wait create-llama to exit - // we don't test install dependencies for now, so just set timeout for 10 seconds - await new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - reject(new Error("create-llama timeout error")); - }, 1000 * 10); - appProcess.on("exit", (code) => { - if (code !== 0 && code !== null) { - clearTimeout(timeout); - reject(new Error("create-llama command was failed!")); - } else { - clearTimeout(timeout); - resolve(undefined); - } - }); - }); - } - - return { - projectName: name, - appProcess, - }; -} - -export async function createTestDir() { - const cwd = path.join(__dirname, "cache", crypto.randomUUID()); - await mkdir(cwd, { recursive: true }); - return cwd; -} diff --git a/packages/create-llama/helpers/constant.ts b/packages/create-llama/helpers/constant.ts deleted file mode 100644 index 64f01be67..000000000 --- a/packages/create-llama/helpers/constant.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const COMMUNITY_OWNER = "run-llama"; -export const COMMUNITY_REPO = "create_llama_projects"; -export const LLAMA_PACK_OWNER = "run-llama"; -export const LLAMA_PACK_REPO = "llama_index"; -export const LLAMA_PACK_FOLDER = "llama-index-packs"; -export const LLAMA_PACK_FOLDER_PATH = `${LLAMA_PACK_OWNER}/${LLAMA_PACK_REPO}/main/${LLAMA_PACK_FOLDER}`; diff --git a/packages/create-llama/helpers/copy.ts b/packages/create-llama/helpers/copy.ts deleted file mode 100644 index a5b722ba3..000000000 --- a/packages/create-llama/helpers/copy.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import { async as glob } from "fast-glob"; -import fs from "fs"; -import path from "path"; - -interface CopyOption { - cwd?: string; - rename?: (basename: string) => string; - parents?: boolean; -} - -const identity = (x: string) => x; - -export const copy = async ( - src: string | string[], - dest: string, - { cwd, rename = identity, parents = true }: CopyOption = {}, -) => { - const source = typeof src === "string" ? [src] : src; - - if (source.length === 0 || !dest) { - throw new TypeError("`src` and `dest` are required"); - } - - const sourceFiles = await glob(source, { - cwd, - dot: true, - absolute: false, - stats: false, - }); - - const destRelativeToCwd = cwd ? path.resolve(cwd, dest) : dest; - - return Promise.all( - sourceFiles.map(async (p) => { - const dirname = path.dirname(p); - const basename = rename(path.basename(p)); - - const from = cwd ? path.resolve(cwd, p) : p; - const to = parents - ? path.join(destRelativeToCwd, dirname, basename) - : path.join(destRelativeToCwd, basename); - - // Ensure the destination directory exists - await fs.promises.mkdir(path.dirname(to), { recursive: true }); - - return fs.promises.copyFile(from, to); - }), - ); -}; diff --git a/packages/create-llama/helpers/devcontainer.ts b/packages/create-llama/helpers/devcontainer.ts deleted file mode 100644 index cb008b977..000000000 --- a/packages/create-llama/helpers/devcontainer.ts +++ /dev/null @@ -1,61 +0,0 @@ -import fs from "fs"; -import path from "path"; -import { TemplateFramework } from "./types"; - -function renderDevcontainerContent( - templatesDir: string, - framework: TemplateFramework, - frontend: boolean, -) { - const devcontainerJson: any = JSON.parse( - fs.readFileSync(path.join(templatesDir, "devcontainer.json"), "utf8"), - ); - - // Modify postCreateCommand - if (frontend) { - devcontainerJson.postCreateCommand = - framework === "fastapi" - ? "cd backend && poetry install && cd ../frontend && npm install" - : "cd backend && npm install && cd ../frontend && npm install"; - } else { - devcontainerJson.postCreateCommand = - framework === "fastapi" ? "poetry install" : "npm install"; - } - - // Modify containerEnv - if (framework === "fastapi") { - if (frontend) { - devcontainerJson.containerEnv = { - ...devcontainerJson.containerEnv, - PYTHONPATH: "${PYTHONPATH}:${workspaceFolder}/backend", - }; - } else { - devcontainerJson.containerEnv = { - ...devcontainerJson.containerEnv, - PYTHONPATH: "${PYTHONPATH}:${workspaceFolder}", - }; - } - } - - return JSON.stringify(devcontainerJson, null, 2); -} - -export const writeDevcontainer = async ( - root: string, - templatesDir: string, - framework: TemplateFramework, - frontend: boolean, -) => { - console.log("Adding .devcontainer"); - const devcontainerContent = renderDevcontainerContent( - templatesDir, - framework, - frontend, - ); - const devcontainerDir = path.join(root, ".devcontainer"); - fs.mkdirSync(devcontainerDir); - await fs.promises.writeFile( - path.join(devcontainerDir, "devcontainer.json"), - devcontainerContent, - ); -}; diff --git a/packages/create-llama/helpers/dir.ts b/packages/create-llama/helpers/dir.ts deleted file mode 100644 index 31ded64cf..000000000 --- a/packages/create-llama/helpers/dir.ts +++ /dev/null @@ -1,3 +0,0 @@ -import path from "path"; - -export const templatesDir = path.join(__dirname, "..", "templates"); diff --git a/packages/create-llama/helpers/env-variables.ts b/packages/create-llama/helpers/env-variables.ts deleted file mode 100644 index f9f6d370a..000000000 --- a/packages/create-llama/helpers/env-variables.ts +++ /dev/null @@ -1,242 +0,0 @@ -import fs from "fs/promises"; -import path from "path"; -import { - FileSourceConfig, - TemplateDataSource, - TemplateFramework, - TemplateVectorDB, -} from "./types"; - -type EnvVar = { - name?: string; - description?: string; - value?: string; -}; - -const renderEnvVar = (envVars: EnvVar[]): string => { - return envVars.reduce( - (prev, env) => - prev + - (env.description - ? `# ${env.description.replaceAll("\n", "\n# ")}\n` - : "") + - (env.name - ? env.value - ? `${env.name}=${env.value}\n\n` - : `# ${env.name}=\n\n` - : ""), - "", - ); -}; - -const getVectorDBEnvs = (vectorDb: TemplateVectorDB) => { - switch (vectorDb) { - case "mongo": - return [ - { - name: "MONGO_URI", - description: - "For generating a connection URI, see https://docs.timescale.com/use-timescale/latest/services/create-a-service\nThe MongoDB connection URI.", - }, - { - name: "MONGODB_DATABASE", - }, - { - name: "MONGODB_VECTORS", - }, - { - name: "MONGODB_VECTOR_INDEX", - }, - ]; - case "pg": - return [ - { - name: "PG_CONNECTION_STRING", - description: - "For generating a connection URI, see https://docs.timescale.com/use-timescale/latest/services/create-a-service\nThe PostgreSQL connection string.", - }, - ]; - - case "pinecone": - return [ - { - name: "PINECONE_API_KEY", - description: - "Configuration for Pinecone vector store\nThe Pinecone API key.", - }, - { - name: "PINECONE_ENVIRONMENT", - }, - { - name: "PINECONE_INDEX_NAME", - }, - ]; - default: - return []; - } -}; - -const getDataSourceEnvs = (dataSource: TemplateDataSource) => { - switch (dataSource.type) { - case "web": - return [ - { - name: "BASE_URL", - description: "The base URL to start web scraping.", - }, - { - name: "URL_PREFIX", - description: "The prefix of the URL to start web scraping.", - }, - { - name: "MAX_DEPTH", - description: "The maximum depth to scrape.", - }, - ]; - default: - return []; - } -}; - -export const createBackendEnvFile = async ( - root: string, - opts: { - openAiKey?: string; - llamaCloudKey?: string; - vectorDb?: TemplateVectorDB; - model?: string; - embeddingModel?: string; - framework?: TemplateFramework; - dataSource?: TemplateDataSource; - port?: number; - }, -) => { - // Init env values - const envFileName = ".env"; - const defaultEnvs = [ - { - render: true, - name: "MODEL", - description: "The name of LLM model to use.", - value: opts.model || "gpt-3.5-turbo", - }, - { - render: true, - name: "OPENAI_API_KEY", - description: "The OpenAI API key to use.", - value: opts.openAiKey, - }, - // Add vector database environment variables - ...(opts.vectorDb ? getVectorDBEnvs(opts.vectorDb) : []), - // Add data source environment variables - ...(opts.dataSource ? getDataSourceEnvs(opts.dataSource) : []), - ]; - let envVars: EnvVar[] = []; - if (opts.framework === "fastapi") { - envVars = [ - ...defaultEnvs, - ...[ - { - 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: opts.port?.toString() || "8000", - }, - { - name: "EMBEDDING_MODEL", - description: "Name of the embedding model to use.", - value: opts.embeddingModel, - }, - { - name: "EMBEDDING_DIM", - description: "Dimension of the embedding model to use.", - }, - { - name: "LLM_TEMPERATURE", - description: "Temperature for sampling from the model.", - }, - { - name: "LLM_MAX_TOKENS", - description: "Maximum number of tokens to generate.", - }, - { - name: "TOP_K", - description: - "The number of similar embeddings to return when retrieving documents.", - value: "3", - }, - { - name: "SYSTEM_PROMPT", - description: `Custom system prompt. -Example: -SYSTEM_PROMPT=" -We have provided context information below. ---------------------- -{context_str} ---------------------- -Given this information, please answer the question: {query_str} -"`, - }, - (opts?.dataSource?.config as FileSourceConfig).useLlamaParse - ? { - name: "LLAMA_CLOUD_API_KEY", - description: `The Llama Cloud API key.`, - value: opts.llamaCloudKey, - } - : {}, - ], - ]; - } else { - envVars = [ - ...defaultEnvs, - ...[ - opts.framework === "nextjs" - ? { - name: "NEXT_PUBLIC_MODEL", - description: - "The LLM model to use (hardcode to front-end artifact).", - value: opts.model || "gpt-3.5-turbo", - } - : {}, - ], - ]; - } - // Render and write env file - const content = renderEnvVar(envVars); - await fs.writeFile(path.join(root, envFileName), content); - console.log(`Created '${envFileName}' file. Please check the settings.`); -}; - -export const createFrontendEnvFile = async ( - root: string, - opts: { - customApiPath?: string; - model?: string; - }, -) => { - const defaultFrontendEnvs = [ - { - name: "MODEL", - description: "The OpenAI model to use.", - value: opts.model, - }, - { - name: "NEXT_PUBLIC_MODEL", - description: "The OpenAI model to use (hardcode to front-end artifact).", - value: opts.model, - }, - { - name: "NEXT_PUBLIC_CHAT_API", - description: "The backend API for chat endpoint.", - value: opts.customApiPath - ? opts.customApiPath - : "http://localhost:8000/api/chat", - }, - ]; - const content = renderEnvVar(defaultFrontendEnvs); - await fs.writeFile(path.join(root, ".env"), content); -}; diff --git a/packages/create-llama/helpers/get-pkg-manager.ts b/packages/create-llama/helpers/get-pkg-manager.ts deleted file mode 100644 index 0187c88a4..000000000 --- a/packages/create-llama/helpers/get-pkg-manager.ts +++ /dev/null @@ -1,15 +0,0 @@ -export type PackageManager = "npm" | "pnpm" | "yarn"; - -export function getPkgManager(): PackageManager { - const userAgent = process.env.npm_config_user_agent || ""; - - if (userAgent.startsWith("yarn")) { - return "yarn"; - } - - if (userAgent.startsWith("pnpm")) { - return "pnpm"; - } - - return "npm"; -} diff --git a/packages/create-llama/helpers/git.ts b/packages/create-llama/helpers/git.ts deleted file mode 100644 index 2cdfe8dc3..000000000 --- a/packages/create-llama/helpers/git.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import { execSync } from "child_process"; -import fs from "fs"; -import path from "path"; - -function isInGitRepository(): boolean { - try { - execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore" }); - return true; - } catch (_) {} - return false; -} - -function isInMercurialRepository(): boolean { - try { - execSync("hg --cwd . root", { stdio: "ignore" }); - return true; - } catch (_) {} - return false; -} - -function isDefaultBranchSet(): boolean { - try { - execSync("git config init.defaultBranch", { stdio: "ignore" }); - return true; - } catch (_) {} - return false; -} - -export function tryGitInit(root: string): boolean { - let didInit = false; - try { - execSync("git --version", { stdio: "ignore" }); - if (isInGitRepository() || isInMercurialRepository()) { - return false; - } - - execSync("git init", { stdio: "ignore" }); - didInit = true; - - if (!isDefaultBranchSet()) { - execSync("git checkout -b main", { stdio: "ignore" }); - } - - execSync("git add -A", { stdio: "ignore" }); - execSync('git commit -m "Initial commit from Create Llama"', { - stdio: "ignore", - }); - return true; - } catch (e) { - if (didInit) { - try { - fs.rmSync(path.join(root, ".git"), { recursive: true, force: true }); - } catch (_) {} - } - return false; - } -} diff --git a/packages/create-llama/helpers/index.ts b/packages/create-llama/helpers/index.ts deleted file mode 100644 index 91280af35..000000000 --- a/packages/create-llama/helpers/index.ts +++ /dev/null @@ -1,192 +0,0 @@ -import { copy } from "./copy"; -import { callPackageManager } from "./install"; - -import fs from "fs/promises"; -import path from "path"; -import { cyan } from "picocolors"; - -import { COMMUNITY_OWNER, COMMUNITY_REPO } from "./constant"; -import { templatesDir } from "./dir"; -import { createBackendEnvFile, createFrontendEnvFile } from "./env-variables"; -import { PackageManager } from "./get-pkg-manager"; -import { installLlamapackProject } from "./llama-pack"; -import { isHavingPoetryLockFile, tryPoetryRun } from "./poetry"; -import { installPythonTemplate } from "./python"; -import { downloadAndExtractRepo } from "./repo"; -import { - FileSourceConfig, - InstallTemplateArgs, - TemplateDataSource, - TemplateFramework, - TemplateVectorDB, -} from "./types"; -import { installTSTemplate } from "./typescript"; - -// eslint-disable-next-line max-params -async function generateContextData( - framework: TemplateFramework, - packageManager?: PackageManager, - openAiKey?: string, - vectorDb?: TemplateVectorDB, - dataSource?: TemplateDataSource, - llamaCloudKey?: string, -) { - if (packageManager) { - const runGenerate = `${cyan( - framework === "fastapi" - ? "poetry run python app/engine/generate.py" - : `${packageManager} run generate`, - )}`; - const openAiKeyConfigured = openAiKey || process.env["OPENAI_API_KEY"]; - const llamaCloudKeyConfigured = (dataSource?.config as FileSourceConfig) - ?.useLlamaParse - ? llamaCloudKey || process.env["LLAMA_CLOUD_API_KEY"] - : true; - const hasVectorDb = vectorDb && vectorDb !== "none"; - if (framework === "fastapi") { - if ( - openAiKeyConfigured && - llamaCloudKeyConfigured && - !hasVectorDb && - isHavingPoetryLockFile() - ) { - console.log(`Running ${runGenerate} to generate the context data.`); - const result = tryPoetryRun("python app/engine/generate.py"); - if (!result) { - console.log(`Failed to run ${runGenerate}.`); - process.exit(1); - } - console.log(`Generated context data`); - return; - } - } else { - if (openAiKeyConfigured && vectorDb === "none") { - console.log(`Running ${runGenerate} to generate the context data.`); - await callPackageManager(packageManager, true, ["run", "generate"]); - return; - } - } - - const settings = []; - if (!openAiKeyConfigured) settings.push("your OpenAI key"); - if (!llamaCloudKeyConfigured) settings.push("your Llama Cloud key"); - if (hasVectorDb) settings.push("your Vector DB environment variables"); - const settingsMessage = - settings.length > 0 ? `After setting ${settings.join(" and ")}, ` : ""; - const generateMessage = `run ${runGenerate} to generate the context data.`; - console.log(`\n${settingsMessage}${generateMessage}\n\n`); - } -} - -const copyContextData = async ( - root: string, - dataSource?: TemplateDataSource, -) => { - const destPath = path.join(root, "data"); - - const dataSourceConfig = dataSource?.config as FileSourceConfig; - - // Copy file - if (dataSource?.type === "file") { - if (dataSourceConfig.path) { - console.log(`\nCopying file to ${cyan(destPath)}\n`); - await fs.mkdir(destPath, { recursive: true }); - await fs.copyFile( - dataSourceConfig.path, - path.join(destPath, path.basename(dataSourceConfig.path)), - ); - } else { - console.log("Missing file path in config"); - process.exit(1); - } - return; - } - - // Copy folder - if (dataSource?.type === "folder") { - const srcPath = - dataSourceConfig.path ?? path.join(templatesDir, "components", "data"); - console.log(`\nCopying data to ${cyan(destPath)}\n`); - await copy("**", destPath, { - parents: true, - cwd: srcPath, - }); - return; - } -}; - -const installCommunityProject = async ({ - root, - communityProjectPath, -}: Pick<InstallTemplateArgs, "root" | "communityProjectPath">) => { - console.log("\nInstalling community project:", communityProjectPath!); - await downloadAndExtractRepo(root, { - username: COMMUNITY_OWNER, - name: COMMUNITY_REPO, - branch: "main", - filePath: communityProjectPath!, - }); -}; - -export const installTemplate = async ( - props: InstallTemplateArgs & { backend: boolean }, -) => { - process.chdir(props.root); - - if (props.template === "community" && props.communityProjectPath) { - await installCommunityProject(props); - return; - } - - if (props.template === "llamapack" && props.llamapack) { - await installLlamapackProject(props); - return; - } - - if (props.framework === "fastapi") { - await installPythonTemplate(props); - } else { - await installTSTemplate(props); - } - - if (props.backend) { - // This is a backend, so we need to copy the test data and create the env file. - - // Copy the environment file to the target directory. - await createBackendEnvFile(props.root, { - openAiKey: props.openAiKey, - llamaCloudKey: props.llamaCloudKey, - vectorDb: props.vectorDb, - model: props.model, - embeddingModel: props.embeddingModel, - framework: props.framework, - dataSource: props.dataSource, - port: props.externalPort, - }); - - if (props.engine === "context") { - await copyContextData(props.root, props.dataSource); - if ( - props.postInstallAction === "runApp" || - props.postInstallAction === "dependencies" - ) { - await generateContextData( - props.framework, - props.packageManager, - props.openAiKey, - props.vectorDb, - props.dataSource, - props.llamaCloudKey, - ); - } - } - } else { - // this is a frontend for a full-stack app, create .env file with model information - createFrontendEnvFile(props.root, { - model: props.model, - customApiPath: props.customApiPath, - }); - } -}; - -export * from "./types"; diff --git a/packages/create-llama/helpers/install.ts b/packages/create-llama/helpers/install.ts deleted file mode 100644 index 9f0f20356..000000000 --- a/packages/create-llama/helpers/install.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import spawn from "cross-spawn"; -import { yellow } from "picocolors"; -import type { PackageManager } from "./get-pkg-manager"; - -/** - * Spawn a package manager installation based on user preference. - * - * @returns A Promise that resolves once the installation is finished. - */ -export async function callPackageManager( - /** Indicate which package manager to use. */ - packageManager: PackageManager, - /** Indicate whether there is an active Internet connection.*/ - isOnline: boolean, - args: string[] = ["install"], -): Promise<void> { - if (!isOnline) { - console.log( - yellow("You appear to be offline.\nFalling back to the local cache."), - ); - args.push("--offline"); - } - /** - * Return a Promise that resolves once the installation is finished. - */ - return new Promise((resolve, reject) => { - /** - * Spawn the installation process. - */ - const child = spawn(packageManager, args, { - stdio: "inherit", - env: { - ...process.env, - ADBLOCK: "1", - // we set NODE_ENV to development as pnpm skips dev - // dependencies when production - NODE_ENV: "development", - DISABLE_OPENCOLLECTIVE: "1", - }, - }); - child.on("close", (code) => { - if (code !== 0) { - reject({ command: `${packageManager} ${args.join(" ")}` }); - return; - } - resolve(); - }); - }); -} diff --git a/packages/create-llama/helpers/is-folder-empty.ts b/packages/create-llama/helpers/is-folder-empty.ts deleted file mode 100644 index 927a344c0..000000000 --- a/packages/create-llama/helpers/is-folder-empty.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import fs from "fs"; -import path from "path"; -import { blue, green } from "picocolors"; - -export function isFolderEmpty(root: string, name: string): boolean { - const validFiles = [ - ".DS_Store", - ".git", - ".gitattributes", - ".gitignore", - ".gitlab-ci.yml", - ".hg", - ".hgcheck", - ".hgignore", - ".idea", - ".npmignore", - ".travis.yml", - "LICENSE", - "Thumbs.db", - "docs", - "mkdocs.yml", - "npm-debug.log", - "yarn-debug.log", - "yarn-error.log", - "yarnrc.yml", - ".yarn", - ]; - - const conflicts = fs - .readdirSync(root) - .filter((file) => !validFiles.includes(file)) - // Support IntelliJ IDEA-based editors - .filter((file) => !/\.iml$/.test(file)); - - if (conflicts.length > 0) { - console.log( - `The directory ${green(name)} contains files that could conflict:`, - ); - console.log(); - for (const file of conflicts) { - try { - const stats = fs.lstatSync(path.join(root, file)); - if (stats.isDirectory()) { - console.log(` ${blue(file)}/`); - } else { - console.log(` ${file}`); - } - } catch { - console.log(` ${file}`); - } - } - console.log(); - console.log( - "Either try using a new directory name, or remove the files listed above.", - ); - console.log(); - return false; - } - - return true; -} diff --git a/packages/create-llama/helpers/is-online.ts b/packages/create-llama/helpers/is-online.ts deleted file mode 100644 index eab698005..000000000 --- a/packages/create-llama/helpers/is-online.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { execSync } from "child_process"; -import dns from "dns"; -import url from "url"; - -function getProxy(): string | undefined { - if (process.env.https_proxy) { - return process.env.https_proxy; - } - - try { - const httpsProxy = execSync("npm config get https-proxy").toString().trim(); - return httpsProxy !== "null" ? httpsProxy : undefined; - } catch (e) { - return; - } -} - -export function getOnline(): Promise<boolean> { - return new Promise((resolve) => { - dns.lookup("registry.yarnpkg.com", (registryErr) => { - if (!registryErr) { - return resolve(true); - } - - const proxy = getProxy(); - if (!proxy) { - return resolve(false); - } - - const { hostname } = url.parse(proxy); - if (!hostname) { - return resolve(false); - } - - dns.lookup(hostname, (proxyErr) => { - resolve(proxyErr == null); - }); - }); - }); -} diff --git a/packages/create-llama/helpers/is-url.ts b/packages/create-llama/helpers/is-url.ts deleted file mode 100644 index eb87b9752..000000000 --- a/packages/create-llama/helpers/is-url.ts +++ /dev/null @@ -1,8 +0,0 @@ -export function isUrl(url: string): boolean { - try { - new URL(url); - return true; - } catch (error) { - return false; - } -} diff --git a/packages/create-llama/helpers/is-writeable.ts b/packages/create-llama/helpers/is-writeable.ts deleted file mode 100644 index fa29d6055..000000000 --- a/packages/create-llama/helpers/is-writeable.ts +++ /dev/null @@ -1,10 +0,0 @@ -import fs from "fs"; - -export async function isWriteable(directory: string): Promise<boolean> { - try { - await fs.promises.access(directory, (fs.constants || fs).W_OK); - return true; - } catch (err) { - return false; - } -} diff --git a/packages/create-llama/helpers/llama-pack.ts b/packages/create-llama/helpers/llama-pack.ts deleted file mode 100644 index 887201d9f..000000000 --- a/packages/create-llama/helpers/llama-pack.ts +++ /dev/null @@ -1,148 +0,0 @@ -import fs from "fs/promises"; -import got from "got"; -import path from "path"; -import { parse } from "smol-toml"; -import { - LLAMA_PACK_FOLDER, - LLAMA_PACK_FOLDER_PATH, - LLAMA_PACK_OWNER, - LLAMA_PACK_REPO, -} from "./constant"; -import { copy } from "./copy"; -import { templatesDir } from "./dir"; -import { addDependencies, installPythonDependencies } from "./python"; -import { getRepoRawContent } from "./repo"; -import { InstallTemplateArgs } from "./types"; - -const getLlamaPackFolderSHA = async () => { - const url = `https://api.github.com/repos/${LLAMA_PACK_OWNER}/${LLAMA_PACK_REPO}/contents`; - const response = await got(url, { - responseType: "json", - }); - const data = response.body as any[]; - const llamaPackFolder = data.find((item) => item.name === LLAMA_PACK_FOLDER); - return llamaPackFolder.sha; -}; - -const getLLamaPackFolderTree = async ( - sha: string, -): Promise< - Array<{ - path: string; - }> -> => { - const url = `https://api.github.com/repos/${LLAMA_PACK_OWNER}/${LLAMA_PACK_REPO}/git/trees/${sha}?recursive=1`; - const response = await got(url, { - responseType: "json", - }); - return (response.body as any).tree; -}; - -export async function getAvailableLlamapackOptions(): Promise< - { - name: string; - folderPath: string; - }[] -> { - const EXAMPLE_RELATIVE_PATH = "/examples/example.py"; - const PACK_FOLDER_SUBFIX = "llama-index-packs"; - - const llamaPackFolderSHA = await getLlamaPackFolderSHA(); - const llamaPackTree = await getLLamaPackFolderTree(llamaPackFolderSHA); - - // Return options that have example files - const exampleFiles = llamaPackTree.filter((item) => - item.path.endsWith(EXAMPLE_RELATIVE_PATH), - ); - const options = exampleFiles.map((file) => { - const packFolder = file.path.substring( - 0, - file.path.indexOf(EXAMPLE_RELATIVE_PATH), - ); - const packName = packFolder.substring(PACK_FOLDER_SUBFIX.length + 1); - return { - name: packName, - folderPath: packFolder, - }; - }); - return options; -} - -const copyLlamapackEmptyProject = async ({ - root, -}: Pick<InstallTemplateArgs, "root">) => { - const templatePath = path.join( - templatesDir, - "components/sample-projects/llamapack", - ); - await copy("**", root, { - parents: true, - cwd: templatePath, - }); -}; - -const copyData = async ({ - root, -}: Pick<InstallTemplateArgs, "root" | "llamapack">) => { - const dataPath = path.join(templatesDir, "components/data"); - await copy("**", path.join(root, "data"), { - parents: true, - cwd: dataPath, - }); -}; - -const installLlamapackExample = async ({ - root, - llamapack, -}: Pick<InstallTemplateArgs, "root" | "llamapack">) => { - const exampleFileName = "example.py"; - const readmeFileName = "README.md"; - const projectTomlFileName = "pyproject.toml"; - const exampleFilePath = `${LLAMA_PACK_FOLDER_PATH}/${llamapack}/examples/${exampleFileName}`; - const readmeFilePath = `${LLAMA_PACK_FOLDER_PATH}/${llamapack}/${readmeFileName}`; - const projectTomlFilePath = `${LLAMA_PACK_FOLDER_PATH}/${llamapack}/${projectTomlFileName}`; - - // Download example.py from llamapack and save to root - const exampleContent = await getRepoRawContent(exampleFilePath); - await fs.writeFile(path.join(root, exampleFileName), exampleContent); - - // Download README.md from llamapack and combine with README-template.md, - // save to root and then delete template file - const readmeContent = await getRepoRawContent(readmeFilePath); - const readmeTemplateContent = await fs.readFile( - path.join(root, "README-template.md"), - "utf-8", - ); - await fs.writeFile( - path.join(root, readmeFileName), - `${readmeContent}\n${readmeTemplateContent}`, - ); - await fs.unlink(path.join(root, "README-template.md")); - - // Download pyproject.toml from llamapack, parse it to get package name and version, - // then add it as a dependency to current toml file in the project - const projectTomlContent = await getRepoRawContent(projectTomlFilePath); - const fileParsed = parse(projectTomlContent) as any; - const packageName = fileParsed.tool.poetry.name; - const packageVersion = fileParsed.tool.poetry.version; - await addDependencies(root, [ - { - name: packageName, - version: packageVersion, - }, - ]); -}; - -export const installLlamapackProject = async ({ - root, - llamapack, - postInstallAction, -}: Pick<InstallTemplateArgs, "root" | "llamapack" | "postInstallAction">) => { - console.log("\nInstalling Llamapack project:", llamapack!); - await copyLlamapackEmptyProject({ root }); - await copyData({ root }); - await installLlamapackExample({ root, llamapack }); - if (postInstallAction === "runApp" || postInstallAction === "dependencies") { - installPythonDependencies({ noRoot: true }); - } -}; diff --git a/packages/create-llama/helpers/make-dir.ts b/packages/create-llama/helpers/make-dir.ts deleted file mode 100644 index 2c258fd6b..000000000 --- a/packages/create-llama/helpers/make-dir.ts +++ /dev/null @@ -1,8 +0,0 @@ -import fs from "fs"; - -export function makeDir( - root: string, - options = { recursive: true }, -): Promise<string | undefined> { - return fs.promises.mkdir(root, options); -} diff --git a/packages/create-llama/helpers/poetry.ts b/packages/create-llama/helpers/poetry.ts deleted file mode 100644 index fe8759d7c..000000000 --- a/packages/create-llama/helpers/poetry.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import { execSync } from "child_process"; -import fs from "fs"; - -export function isPoetryAvailable(): boolean { - try { - execSync("poetry --version", { stdio: "ignore" }); - return true; - } catch (_) {} - return false; -} - -export function tryPoetryInstall(noRoot: boolean): boolean { - try { - execSync(`poetry install${noRoot ? " --no-root" : ""}`, { - stdio: "inherit", - }); - return true; - } catch (_) {} - return false; -} - -export function tryPoetryRun(command: string): boolean { - try { - execSync(`poetry run ${command}`, { stdio: "inherit" }); - return true; - } catch (_) {} - return false; -} - -export function isHavingPoetryLockFile(): boolean { - try { - return fs.existsSync("poetry.lock"); - } catch (_) {} - return false; -} diff --git a/packages/create-llama/helpers/python.ts b/packages/create-llama/helpers/python.ts deleted file mode 100644 index 285beb9a8..000000000 --- a/packages/create-llama/helpers/python.ts +++ /dev/null @@ -1,272 +0,0 @@ -import fs from "fs/promises"; -import path from "path"; -import { cyan, red } from "picocolors"; -import { parse, stringify } from "smol-toml"; -import terminalLink from "terminal-link"; -import { copy } from "./copy"; -import { templatesDir } from "./dir"; -import { isPoetryAvailable, tryPoetryInstall } from "./poetry"; -import { Tool } from "./tools"; -import { - FileSourceConfig, - InstallTemplateArgs, - TemplateDataSource, - TemplateVectorDB, -} from "./types"; - -interface Dependency { - name: string; - version?: string; - extras?: string[]; -} - -const getAdditionalDependencies = ( - vectorDb?: TemplateVectorDB, - dataSource?: TemplateDataSource, - tools?: Tool[], -) => { - const dependencies: Dependency[] = []; - - // Add vector db dependencies - switch (vectorDb) { - case "mongo": { - dependencies.push({ - name: "llama-index-vector-stores-mongodb", - version: "^0.1.3", - }); - break; - } - case "pg": { - dependencies.push({ - name: "llama-index-vector-stores-postgres", - version: "^0.1.1", - }); - } - case "pinecone": { - dependencies.push({ - name: "llama-index-vector-stores-pinecone", - version: "^0.1.3", - }); - break; - } - } - - // Add data source dependencies - const dataSourceType = dataSource?.type; - if (dataSourceType === "file" || dataSourceType === "folder") { - // llama-index-readers-file (pdf, excel, csv) is already included in llama_index package - dependencies.push({ - name: "docx2txt", - version: "^0.8", - }); - } else if (dataSourceType === "web") { - dependencies.push({ - name: "llama-index-readers-web", - version: "^0.1.6", - }); - } - - // Add tools dependencies - tools?.forEach((tool) => { - tool.dependencies?.forEach((dep) => { - dependencies.push(dep); - }); - }); - - return dependencies; -}; - -const mergePoetryDependencies = ( - dependencies: Dependency[], - existingDependencies: Record<string, Omit<Dependency, "name">>, -) => { - for (const dependency of dependencies) { - let value = existingDependencies[dependency.name] ?? {}; - - // default string value is equal to attribute "version" - if (typeof value === "string") { - value = { version: value }; - } - - value.version = dependency.version ?? value.version; - value.extras = dependency.extras ?? value.extras; - - if (value.version === undefined) { - throw new Error( - `Dependency "${dependency.name}" is missing attribute "version"!`, - ); - } - - existingDependencies[dependency.name] = value; - } -}; - -export const addDependencies = async ( - projectDir: string, - dependencies: Dependency[], -) => { - if (dependencies.length === 0) return; - - const FILENAME = "pyproject.toml"; - try { - // Parse toml file - const file = path.join(projectDir, FILENAME); - const fileContent = await fs.readFile(file, "utf8"); - const fileParsed = parse(fileContent); - - // Modify toml dependencies - const tool = fileParsed.tool as any; - const existingDependencies = tool.poetry.dependencies; - mergePoetryDependencies(dependencies, existingDependencies); - - // Write toml file - const newFileContent = stringify(fileParsed); - await fs.writeFile(file, newFileContent); - - const dependenciesString = dependencies.map((d) => d.name).join(", "); - console.log(`\nAdded ${dependenciesString} to ${cyan(FILENAME)}\n`); - } catch (error) { - console.log( - `Error while updating dependencies for Poetry project file ${FILENAME}\n`, - error, - ); - } -}; - -export const installPythonDependencies = ( - { noRoot }: { noRoot: boolean } = { noRoot: false }, -) => { - if (isPoetryAvailable()) { - console.log( - `Installing python dependencies using poetry. This may take a while...`, - ); - const installSuccessful = tryPoetryInstall(noRoot); - if (!installSuccessful) { - console.error( - red( - "Installing dependencies using poetry failed. Please check error log above and try running create-llama again.", - ), - ); - process.exit(1); - } - } else { - console.error( - red( - `Poetry is not available in the current environment. Please check ${terminalLink( - "Poetry Installation", - `https://python-poetry.org/docs/#installation`, - )} to install poetry first, then run create-llama again.`, - ), - ); - process.exit(1); - } -}; - -export const installPythonTemplate = async ({ - root, - template, - framework, - engine, - vectorDb, - dataSource, - tools, - postInstallAction, -}: Pick< - InstallTemplateArgs, - | "root" - | "framework" - | "template" - | "engine" - | "vectorDb" - | "dataSource" - | "tools" - | "postInstallAction" ->) => { - console.log("\nInitializing Python project with template:", template, "\n"); - const templatePath = path.join(templatesDir, "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 enginePath = path.join(root, "app", "engine"); - const compPath = path.join(templatesDir, "components"); - - const vectorDbDirName = vectorDb ?? "none"; - const VectorDBPath = path.join( - compPath, - "vectordbs", - "python", - vectorDbDirName, - ); - await copy("**", enginePath, { - parents: true, - cwd: VectorDBPath, - }); - - // Copy engine code - if (tools !== undefined && tools.length > 0) { - await copy("**", enginePath, { - parents: true, - cwd: path.join(compPath, "engines", "python", "agent"), - }); - // Write tools_config.json - const configContent: Record<string, any> = {}; - tools.forEach((tool) => { - configContent[tool.name] = tool.config ?? {}; - }); - const configFilePath = path.join(root, "tools_config.json"); - await fs.writeFile( - configFilePath, - JSON.stringify(configContent, null, 2), - ); - } else { - await copy("**", enginePath, { - parents: true, - cwd: path.join(compPath, "engines", "python", "chat"), - }); - } - - const dataSourceType = dataSource?.type; - if (dataSourceType !== undefined && dataSourceType !== "none") { - let loaderFolder: string; - if (dataSourceType === "file" || dataSourceType === "folder") { - const dataSourceConfig = dataSource?.config as FileSourceConfig; - loaderFolder = dataSourceConfig.useLlamaParse ? "llama_parse" : "file"; - } else { - loaderFolder = dataSourceType; - } - await copy("**", enginePath, { - parents: true, - cwd: path.join(compPath, "loaders", "python", loaderFolder), - }); - } - } - - const addOnDependencies = getAdditionalDependencies( - vectorDb, - dataSource, - tools, - ); - await addDependencies(root, addOnDependencies); - - if (postInstallAction === "runApp" || postInstallAction === "dependencies") { - installPythonDependencies(); - } -}; diff --git a/packages/create-llama/helpers/repo.ts b/packages/create-llama/helpers/repo.ts deleted file mode 100644 index 3942c28cd..000000000 --- a/packages/create-llama/helpers/repo.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { createWriteStream, promises } from "fs"; -import got from "got"; -import { tmpdir } from "os"; -import { join } from "path"; -import { Stream } from "stream"; -import tar from "tar"; -import { promisify } from "util"; -import { makeDir } from "./make-dir"; - -export type RepoInfo = { - username: string; - name: string; - branch: string; - filePath: string; -}; - -const pipeline = promisify(Stream.pipeline); - -async function downloadTar(url: string) { - const tempFile = join(tmpdir(), `next.js-cna-example.temp-${Date.now()}`); - await pipeline(got.stream(url), createWriteStream(tempFile)); - return tempFile; -} - -export async function downloadAndExtractRepo( - root: string, - { username, name, branch, filePath }: RepoInfo, -) { - await makeDir(root); - - const tempFile = await downloadTar( - `https://codeload.github.com/${username}/${name}/tar.gz/${branch}`, - ); - - await tar.x({ - file: tempFile, - cwd: root, - strip: filePath ? filePath.split("/").length + 1 : 1, - filter: (p) => - p.startsWith( - `${name}-${branch.replace(/\//g, "-")}${ - filePath ? `/${filePath}/` : "/" - }`, - ), - }); - - await promises.unlink(tempFile); -} - -export async function getRepoRootFolders( - owner: string, - repo: string, -): Promise<string[]> { - const url = `https://api.github.com/repos/${owner}/${repo}/contents`; - - const response = await got(url, { - responseType: "json", - }); - - const data = response.body as any[]; - const folders = data.filter((item) => item.type === "dir"); - return folders.map((item) => item.name); -} - -export async function getRepoRawContent(repoFilePath: string) { - const url = `https://raw.githubusercontent.com/${repoFilePath}`; - const response = await got(url, { - responseType: "text", - }); - return response.body; -} diff --git a/packages/create-llama/helpers/run-app.ts b/packages/create-llama/helpers/run-app.ts deleted file mode 100644 index 616787a73..000000000 --- a/packages/create-llama/helpers/run-app.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { ChildProcess, SpawnOptions, spawn } from "child_process"; -import path from "path"; -import { TemplateFramework } from "./types"; - -const createProcess = ( - command: string, - args: string[], - options: SpawnOptions, -) => { - return spawn(command, args, { - ...options, - shell: true, - }) - .on("exit", function (code) { - if (code !== 0) { - console.log(`Child process exited with code=${code}`); - process.exit(1); - } - }) - .on("error", function (err) { - console.log("Error when running chill process: ", err); - process.exit(1); - }); -}; - -// eslint-disable-next-line max-params -export async function runApp( - appPath: string, - frontend: boolean, - framework: TemplateFramework, - port?: number, - externalPort?: number, -): Promise<any> { - let backendAppProcess: ChildProcess; - let frontendAppProcess: ChildProcess | undefined; - const frontendPort = port || 3000; - let backendPort = externalPort || 8000; - - // Callback to kill app processes - process.on("exit", () => { - console.log("Killing app processes..."); - backendAppProcess.kill(); - frontendAppProcess?.kill(); - }); - - let backendCommand = ""; - let backendArgs: string[]; - if (framework === "fastapi") { - backendCommand = "poetry"; - backendArgs = [ - "run", - "uvicorn", - "main:app", - "--host=0.0.0.0", - "--port=" + backendPort, - ]; - } else if (framework === "nextjs") { - backendCommand = "npm"; - backendArgs = ["run", "dev"]; - backendPort = frontendPort; - } else { - backendCommand = "npm"; - backendArgs = ["run", "dev"]; - } - - if (frontend) { - return new Promise((resolve, reject) => { - backendAppProcess = createProcess(backendCommand, backendArgs, { - stdio: "inherit", - cwd: path.join(appPath, "backend"), - env: { ...process.env, PORT: `${backendPort}` }, - }); - frontendAppProcess = createProcess("npm", ["run", "dev"], { - stdio: "inherit", - cwd: path.join(appPath, "frontend"), - env: { ...process.env, PORT: `${frontendPort}` }, - }); - }); - } else { - return new Promise((resolve, reject) => { - backendAppProcess = createProcess(backendCommand, backendArgs, { - stdio: "inherit", - cwd: path.join(appPath), - env: { ...process.env, PORT: `${backendPort}` }, - }); - }); - } -} diff --git a/packages/create-llama/helpers/tools.ts b/packages/create-llama/helpers/tools.ts deleted file mode 100644 index 495592537..000000000 --- a/packages/create-llama/helpers/tools.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { red } from "picocolors"; - -export type Tool = { - display: string; - name: string; - config?: Record<string, any>; - dependencies?: ToolDependencies[]; -}; -export type ToolDependencies = { - name: string; - version?: string; -}; - -export const supportedTools: Tool[] = [ - { - display: "Google Search (configuration required after installation)", - name: "google.GoogleSearchToolSpec", - config: { - engine: - "Your search engine id, see https://developers.google.com/custom-search/v1/overview#prerequisites", - key: "Your search api key", - num: 2, - }, - dependencies: [ - { - name: "llama-index-tools-google", - version: "0.1.2", - }, - ], - }, - { - display: "Wikipedia", - name: "wikipedia.WikipediaToolSpec", - dependencies: [ - { - name: "llama-index-tools-wikipedia", - version: "0.1.2", - }, - ], - }, -]; - -export const getTool = (toolName: string): Tool | undefined => { - return supportedTools.find((tool) => tool.name === toolName); -}; - -export const getTools = (toolsName: string[]): Tool[] => { - const tools: Tool[] = []; - for (const toolName of toolsName) { - const tool = getTool(toolName); - if (!tool) { - console.log( - red( - `Error: Tool '${toolName}' is not supported. Supported tools are: ${supportedTools - .map((t) => t.name) - .join(", ")}`, - ), - ); - process.exit(1); - } - tools.push(tool); - } - return tools; -}; - -export const toolsRequireConfig = (tools?: Tool[]): boolean => { - if (tools) { - return tools?.some((tool) => Object.keys(tool.config || {}).length > 0); - } - return false; -}; diff --git a/packages/create-llama/helpers/types.ts b/packages/create-llama/helpers/types.ts deleted file mode 100644 index 76be9af31..000000000 --- a/packages/create-llama/helpers/types.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { PackageManager } from "../helpers/get-pkg-manager"; -import { Tool } from "./tools"; - -export type TemplateType = "simple" | "streaming" | "community" | "llamapack"; -export type TemplateFramework = "nextjs" | "express" | "fastapi"; -export type TemplateEngine = "simple" | "context"; -export type TemplateUI = "html" | "shadcn"; -export type TemplateVectorDB = "none" | "mongo" | "pg" | "pinecone"; -export type TemplatePostInstallAction = - | "none" - | "VSCode" - | "dependencies" - | "runApp"; -export type TemplateDataSource = { - type: TemplateDataSourceType; - config: TemplateDataSourceConfig; -}; -export type TemplateDataSourceType = "none" | "file" | "folder" | "web"; -// Config for both file and folder -export type FileSourceConfig = { - path?: string; - useLlamaParse?: boolean; -}; -export type WebSourceConfig = { - baseUrl?: string; - depth?: number; -}; -export type TemplateDataSourceConfig = FileSourceConfig | WebSourceConfig; - -export interface InstallTemplateArgs { - appName: string; - root: string; - packageManager: PackageManager; - isOnline: boolean; - template: TemplateType; - framework: TemplateFramework; - engine: TemplateEngine; - ui: TemplateUI; - dataSource?: TemplateDataSource; - eslint: boolean; - customApiPath?: string; - openAiKey?: string; - llamaCloudKey?: string; - model: string; - embeddingModel: string; - communityProjectPath?: string; - llamapack?: string; - vectorDb?: TemplateVectorDB; - externalPort?: number; - postInstallAction?: TemplatePostInstallAction; - tools?: Tool[]; -} diff --git a/packages/create-llama/helpers/typescript.ts b/packages/create-llama/helpers/typescript.ts deleted file mode 100644 index e607f070b..000000000 --- a/packages/create-llama/helpers/typescript.ts +++ /dev/null @@ -1,227 +0,0 @@ -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 { templatesDir } from "./dir"; -import { PackageManager } from "./get-pkg-manager"; -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; - } - } -}; - -export const installTSDependencies = async ( - packageJson: any, - packageManager: PackageManager, - isOnline: boolean, -): Promise<void> => { - 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).catch((error) => { - console.error("Failed to install TS dependencies. Exiting..."); - process.exit(1); - }); -}; - -/** - * Install a LlamaIndex internal template to a given `root` directory. - */ -export const installTSTemplate = async ({ - appName, - root, - packageManager, - isOnline, - template, - framework, - engine, - ui, - eslint, - customApiPath, - vectorDb, - postInstallAction, - backend, -}: InstallTemplateArgs & { backend: boolean }) => { - 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(templatesDir, "types", template, framework); - const copySource = ["**"]; - if (!eslint) copySource.push("!eslintrc.json"); - - await copy(copySource, root, { - parents: true, - cwd: templatePath, - rename, - }); - - /** - * If next.js is not used as a backend, update next.config.js to use static site generation. - */ - if (framework === "nextjs" && !backend) { - // update next.config.json for static site generation - const nextConfigJsonFile = path.join(root, "next.config.json"); - const nextConfigJson: any = JSON.parse( - await fs.readFile(nextConfigJsonFile, "utf8"), - ); - nextConfigJson.output = "export"; - nextConfigJson.images = { unoptimized: true }; - await fs.writeFile( - nextConfigJsonFile, - JSON.stringify(nextConfigJson, null, 2) + os.EOL, - ); - } - - /** - * Copy the selected chat engine files to the target directory and reference it. - */ - let relativeEngineDestPath; - const compPath = path.join(templatesDir, "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 - } - - 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, - ); - - if (postInstallAction === "runApp" || postInstallAction === "dependencies") { - await installTSDependencies(packageJson, packageManager, isOnline); - } -}; diff --git a/packages/create-llama/helpers/validate-pkg.ts b/packages/create-llama/helpers/validate-pkg.ts deleted file mode 100644 index 68317653c..000000000 --- a/packages/create-llama/helpers/validate-pkg.ts +++ /dev/null @@ -1,20 +0,0 @@ -// eslint-disable-next-line import/no-extraneous-dependencies -import validateProjectName from "validate-npm-package-name"; - -export function validateNpmName(name: string): { - valid: boolean; - problems?: string[]; -} { - const nameValidation = validateProjectName(name); - if (nameValidation.validForNewPackages) { - return { valid: true }; - } - - return { - valid: false, - problems: [ - ...(nameValidation.errors || []), - ...(nameValidation.warnings || []), - ], - }; -} diff --git a/packages/create-llama/index.ts b/packages/create-llama/index.ts deleted file mode 100644 index 131d6eba2..000000000 --- a/packages/create-llama/index.ts +++ /dev/null @@ -1,383 +0,0 @@ -#!/usr/bin/env node -/* eslint-disable import/no-extraneous-dependencies */ -import { execSync } from "child_process"; -import Commander from "commander"; -import Conf from "conf"; -import fs from "fs"; -import path from "path"; -import { bold, cyan, green, red, yellow } from "picocolors"; -import prompts from "prompts"; -import terminalLink from "terminal-link"; -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 { runApp } from "./helpers/run-app"; -import { getTools } from "./helpers/tools"; -import { validateNpmName } from "./helpers/validate-pkg"; -import packageJson from "./package.json"; -import { QuestionArgs, askQuestions, onPromptState } from "./questions"; - -let projectPath: string = ""; - -const handleSigTerm = () => process.exit(0); - -process.on("SIGINT", handleSigTerm); -process.on("SIGTERM", handleSigTerm); - -const program = new Commander.Command(packageJson.name) - .version(packageJson.version) - .arguments("<project-directory>") - .usage(`${green("<project-directory>")} [options]`) - .action((name) => { - projectPath = name; - }) - .option( - "--eslint", - ` - - Initialize with eslint config. -`, - ) - .option( - "--use-npm", - ` - - Explicitly tell the CLI to bootstrap the application using npm -`, - ) - .option( - "--use-pnpm", - ` - - Explicitly tell the CLI to bootstrap the application using pnpm -`, - ) - .option( - "--use-yarn", - ` - - Explicitly tell the CLI to bootstrap the application using Yarn -`, - ) - .option( - "--reset-preferences", - ` - - Explicitly tell the CLI to reset any stored preferences -`, - ) - .option( - "--template <template>", - ` - - Select a template to bootstrap the application with. -`, - ) - .option( - "--engine <engine>", - ` - - Select a chat engine to bootstrap the application with. -`, - ) - .option( - "--framework <framework>", - ` - - Select a framework to bootstrap the application with. -`, - ) - .option( - "--files <path>", - ` - - Specify the path to a local file or folder for chatting. -`, - ) - .option( - "--open-ai-key <key>", - ` - - Provide an OpenAI API key. -`, - ) - .option( - "--ui <ui>", - ` - - Select a UI to bootstrap the application with. -`, - ) - .option( - "--frontend", - ` - - Whether to generate a frontend for your backend. -`, - ) - .option( - "--model <model>", - ` - - Select OpenAI model to use. E.g. gpt-3.5-turbo. -`, - ) - .option( - "--embedding-model <embeddingModel>", - ` - Select OpenAI embedding model to use. E.g. text-embedding-ada-002. -`, - ) - .option( - "--port <port>", - ` - - Select UI port. -`, - ) - .option( - "--external-port <external>", - ` - - Select external port. -`, - ) - .option( - "--post-install-action <action>", - ` - - Choose an action after installation. For example, 'runApp' or 'dependencies'. The default option is just to generate the app. -`, - ) - .option( - "--vector-db <vectorDb>", - ` - - Select which vector database you would like to use, such as 'none', 'pg' or 'mongo'. The default option is not to use a vector database and use the local filesystem instead ('none'). -`, - ) - .option( - "--tools <tools>", - ` - - Specify the tools you want to use by providing a comma-separated list. For example, 'wikipedia.WikipediaToolSpec,google.GoogleSearchToolSpec'. Use 'none' to not using any tools. -`, - ) - .option( - "--llama-parse", - ` - Enable LlamaParse. -`, - ) - .option( - "--llama-cloud-key <key>", - ` - Provide a LlamaCloud API key. -`, - ) - .allowUnknownOption() - .parse(process.argv); -if (process.argv.includes("--no-frontend")) { - program.frontend = false; -} -if (process.argv.includes("--no-eslint")) { - program.eslint = false; -} -if (process.argv.includes("--tools")) { - if (program.tools === "none") { - program.tools = []; - } else { - program.tools = getTools(program.tools.split(",")); - } -} -if (process.argv.includes("--no-llama-parse")) { - program.llamaParse = false; -} - -const packageManager = !!program.useNpm - ? "npm" - : !!program.usePnpm - ? "pnpm" - : !!program.useYarn - ? "yarn" - : getPkgManager(); - -async function run(): Promise<void> { - const conf = new Conf({ projectName: "create-llama" }); - - if (program.resetPreferences) { - conf.clear(); - console.log(`Preferences reset successfully`); - return; - } - - if (typeof projectPath === "string") { - projectPath = projectPath.trim(); - } - - if (!projectPath) { - const res = await prompts({ - onState: onPromptState, - type: "text", - name: "path", - message: "What is your project named?", - initial: "my-app", - validate: (name) => { - const validation = validateNpmName(path.basename(path.resolve(name))); - if (validation.valid) { - return true; - } - return "Invalid project name: " + validation.problems![0]; - }, - }); - - if (typeof res.path === "string") { - projectPath = res.path.trim(); - } - } - - if (!projectPath) { - console.log( - "\nPlease specify the project directory:\n" + - ` ${cyan(program.name())} ${green("<project-directory>")}\n` + - "For example:\n" + - ` ${cyan(program.name())} ${green("my-app")}\n\n` + - `Run ${cyan(`${program.name()} --help`)} to see all options.`, - ); - process.exit(1); - } - - const resolvedProjectPath = path.resolve(projectPath); - const projectName = path.basename(resolvedProjectPath); - - const { valid, problems } = validateNpmName(projectName); - if (!valid) { - console.error( - `Could not create a project called ${red( - `"${projectName}"`, - )} because of npm naming restrictions:`, - ); - - problems!.forEach((p) => console.error(` ${red(bold("*"))} ${p}`)); - process.exit(1); - } - - /** - * Verify the project dir is empty or doesn't exist - */ - const root = path.resolve(resolvedProjectPath); - const appName = path.basename(root); - const folderExists = fs.existsSync(root); - - if (folderExists && !isFolderEmpty(root, appName)) { - process.exit(1); - } - - const preferences = (conf.get("preferences") || {}) as QuestionArgs; - await askQuestions(program as unknown as QuestionArgs, preferences); - - await createApp({ - template: program.template, - framework: program.framework, - engine: program.engine, - ui: program.ui, - appPath: resolvedProjectPath, - packageManager, - eslint: program.eslint, - frontend: program.frontend, - openAiKey: program.openAiKey, - llamaCloudKey: program.llamaCloudKey, - model: program.model, - embeddingModel: program.embeddingModel, - communityProjectPath: program.communityProjectPath, - llamapack: program.llamapack, - vectorDb: program.vectorDb, - externalPort: program.externalPort, - postInstallAction: program.postInstallAction, - dataSource: program.dataSource, - tools: program.tools, - }); - conf.set("preferences", preferences); - - if (program.postInstallAction === "VSCode") { - console.log(`Starting VSCode in ${root}...`); - try { - execSync(`code . --new-window --goto README.md`, { - stdio: "inherit", - cwd: root, - }); - } catch (error) { - console.log( - red( - `Failed to start VSCode in ${root}. -Got error: ${(error as Error).message}.\n`, - ), - ); - console.log( - `Make sure you have VSCode installed and added to your PATH (shell alias will not work). -Please check ${cyan( - terminalLink( - "This documentation", - `https://code.visualstudio.com/docs/setup/setup-overview`, - ), - )} for more information.`, - ); - } - } else if (program.postInstallAction === "runApp") { - console.log(`Running app in ${root}...`); - await runApp( - root, - program.frontend, - program.framework, - program.port, - program.externalPort, - ); - } -} - -const update = checkForUpdate(packageJson).catch(() => null); - -async function notifyUpdate(): Promise<void> { - try { - const res = await update; - if (res?.latest) { - const updateMessage = - packageManager === "yarn" - ? "yarn global add create-llama@latest" - : packageManager === "pnpm" - ? "pnpm add -g create-llama@latest" - : "npm i -g create-llama@latest"; - - console.log( - yellow(bold("A new version of `create-llama` is available!")) + - "\n" + - "You can update by running: " + - cyan(updateMessage) + - "\n", - ); - } - } catch { - // ignore error - } -} - -run() - .then(notifyUpdate) - .catch(async (reason) => { - console.log(); - console.log("Aborting installation."); - if (reason.command) { - console.log(` ${cyan(reason.command)} has failed.`); - } else { - console.log( - red("Unexpected error. Please report it as a bug:") + "\n", - reason, - ); - } - console.log(); - - await notifyUpdate(); - - process.exit(1); - }); diff --git a/packages/create-llama/package.json b/packages/create-llama/package.json deleted file mode 100644 index 3ed42e94d..000000000 --- a/packages/create-llama/package.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "name": "create-llama", - "version": "0.0.28", - "keywords": [ - "rag", - "llamaindex", - "next.js" - ], - "description": "Create LlamaIndex-powered apps with one command", - "repository": { - "type": "git", - "url": "https://github.com/run-llama/LlamaIndexTS", - "directory": "packages/create-llama" - }, - "license": "MIT", - "bin": { - "create-llama": "./dist/index.js" - }, - "files": [ - "dist" - ], - "scripts": { - "clean": "rimraf --glob ./dist ./templates/**/__pycache__ ./templates/**/node_modules ./templates/**/poetry.lock", - "dev": "ncc build ./index.ts -w -o dist/", - "build": "npm run clean && ncc build ./index.ts -o ./dist/ --minify --no-cache --no-source-map-register", - "lint": "eslint . --ignore-pattern dist --ignore-pattern e2e/cache", - "e2e": "playwright test", - "prepublishOnly": "cd ../../ && pnpm run build:release" - }, - "devDependencies": { - "@playwright/test": "^1.41.1", - "@types/async-retry": "1.4.2", - "@types/ci-info": "2.0.0", - "@types/cross-spawn": "6.0.0", - "@types/node": "^20.11.7", - "@types/prompts": "2.0.1", - "@types/tar": "6.1.5", - "@types/validate-npm-package-name": "3.0.0", - "@vercel/ncc": "0.38.1", - "async-retry": "1.3.1", - "async-sema": "3.0.1", - "ci-info": "github:watson/ci-info#f43f6a1cefff47fb361c88cf4b943fdbcaafe540", - "commander": "2.20.0", - "conf": "10.2.0", - "cross-spawn": "7.0.3", - "fast-glob": "3.3.1", - "got": "10.7.0", - "picocolors": "1.0.0", - "prompts": "2.1.0", - "rimraf": "^5.0.5", - "smol-toml": "^1.1.4", - "tar": "6.1.15", - "terminal-link": "^3.0.0", - "update-check": "1.5.4", - "validate-npm-package-name": "3.0.0", - "wait-port": "^1.1.0" - }, - "engines": { - "node": ">=16.14.0" - } -} diff --git a/packages/create-llama/playwright.config.ts b/packages/create-llama/playwright.config.ts deleted file mode 100644 index 0b4b420b7..000000000 --- a/packages/create-llama/playwright.config.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* eslint-disable turbo/no-undeclared-env-vars */ -import { defineConfig, devices } from "@playwright/test"; - -export default defineConfig({ - testDir: "./e2e", - fullyParallel: true, - forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, - workers: process.env.CI ? 1 : undefined, - timeout: 1000 * 60 * 5, - reporter: "html", - use: { - trace: "on-first-retry", - }, - projects: [ - { - name: "chromium", - use: { ...devices["Desktop Chrome"] }, - }, - ], -}); diff --git a/packages/create-llama/questions.ts b/packages/create-llama/questions.ts deleted file mode 100644 index af5f17d69..000000000 --- a/packages/create-llama/questions.ts +++ /dev/null @@ -1,747 +0,0 @@ -import { execSync } from "child_process"; -import ciInfo from "ci-info"; -import fs from "fs"; -import path from "path"; -import { blue, green, red } from "picocolors"; -import prompts from "prompts"; -import { InstallAppArgs } from "./create-app"; -import { - FileSourceConfig, - TemplateDataSourceType, - TemplateFramework, -} from "./helpers"; -import { COMMUNITY_OWNER, COMMUNITY_REPO } from "./helpers/constant"; -import { templatesDir } from "./helpers/dir"; -import { getAvailableLlamapackOptions } from "./helpers/llama-pack"; -import { getRepoRootFolders } from "./helpers/repo"; -import { supportedTools, toolsRequireConfig } from "./helpers/tools"; - -export type QuestionArgs = Omit< - InstallAppArgs, - "appPath" | "packageManager" -> & { files?: string; llamaParse?: boolean }; -const supportedContextFileTypes = [ - ".pdf", - ".doc", - ".docx", - ".xls", - ".xlsx", - ".csv", -]; -const MACOS_FILE_SELECTION_SCRIPT = ` -osascript -l JavaScript -e ' - a = Application.currentApplication(); - a.includeStandardAdditions = true; - a.chooseFile({ withPrompt: "Please select a file to process:" }).toString() -'`; -const MACOS_FOLDER_SELECTION_SCRIPT = ` -osascript -l JavaScript -e ' - a = Application.currentApplication(); - a.includeStandardAdditions = true; - a.chooseFolder({ withPrompt: "Please select a folder to process:" }).toString() -'`; -const WINDOWS_FILE_SELECTION_SCRIPT = ` -Add-Type -AssemblyName System.Windows.Forms -$openFileDialog = New-Object System.Windows.Forms.OpenFileDialog -$openFileDialog.InitialDirectory = [Environment]::GetFolderPath('Desktop') -$result = $openFileDialog.ShowDialog() -if ($result -eq 'OK') { - $openFileDialog.FileName -} -`; -const WINDOWS_FOLDER_SELECTION_SCRIPT = ` -Add-Type -AssemblyName System.windows.forms -$folderBrowser = New-Object System.Windows.Forms.FolderBrowserDialog -$dialogResult = $folderBrowser.ShowDialog() -if ($dialogResult -eq [System.Windows.Forms.DialogResult]::OK) -{ - $folderBrowser.SelectedPath -} -`; - -const defaults: QuestionArgs = { - template: "streaming", - framework: "nextjs", - engine: "simple", - ui: "html", - eslint: true, - frontend: false, - openAiKey: "", - llamaCloudKey: "", - model: "gpt-3.5-turbo", - embeddingModel: "text-embedding-ada-002", - communityProjectPath: "", - llamapack: "", - postInstallAction: "dependencies", - dataSource: { - type: "none", - config: {}, - }, - tools: [], -}; - -const handlers = { - onCancel: () => { - console.error("Exiting."); - process.exit(1); - }, -}; - -const getVectorDbChoices = (framework: TemplateFramework) => { - const choices = [ - { - title: "No, just store the data in the file system", - value: "none", - }, - { title: "MongoDB", value: "mongo" }, - { title: "PostgreSQL", value: "pg" }, - { title: "Pinecone", value: "pinecone" }, - ]; - - const vectordbLang = framework === "fastapi" ? "python" : "typescript"; - const compPath = path.join(templatesDir, "components"); - const vectordbPath = path.join(compPath, "vectordbs", vectordbLang); - - const availableChoices = fs - .readdirSync(vectordbPath) - .filter((file) => fs.statSync(path.join(vectordbPath, file)).isDirectory()); - - const displayedChoices = choices.filter((choice) => - availableChoices.includes(choice.value), - ); - - return displayedChoices; -}; - -const getDataSourceChoices = (framework: TemplateFramework) => { - const choices = [ - { - title: "No data, just a simple chat", - value: "simple", - }, - { title: "Use an example PDF", value: "exampleFile" }, - ]; - if (process.platform === "win32" || process.platform === "darwin") { - choices.push({ - title: `Use a local file (${supportedContextFileTypes.join(", ")})`, - value: "localFile", - }); - choices.push({ - title: `Use a local folder`, - value: "localFolder", - }); - } - if (framework === "fastapi") { - choices.push({ - title: "Use website content (requires Chrome)", - value: "web", - }); - } - return choices; -}; - -const selectLocalContextData = async (type: TemplateDataSourceType) => { - try { - let selectedPath: string = ""; - let execScript: string; - let execOpts: any = {}; - switch (process.platform) { - case "win32": // Windows - execScript = - type === "file" - ? WINDOWS_FILE_SELECTION_SCRIPT - : WINDOWS_FOLDER_SELECTION_SCRIPT; - execOpts = { shell: "powershell.exe" }; - break; - case "darwin": // MacOS - execScript = - type === "file" - ? MACOS_FILE_SELECTION_SCRIPT - : MACOS_FOLDER_SELECTION_SCRIPT; - break; - default: // Unsupported OS - console.log(red("Unsupported OS error!")); - process.exit(1); - } - selectedPath = execSync(execScript, execOpts).toString().trim(); - if (type === "file") { - const fileType = path.extname(selectedPath); - if (!supportedContextFileTypes.includes(fileType)) { - console.log( - red( - `Please select a supported file type: ${supportedContextFileTypes}`, - ), - ); - process.exit(1); - } - } - return selectedPath; - } catch (error) { - console.log( - red( - "Got an error when trying to select local context data! Please try again or select another data source option.", - ), - ); - process.exit(1); - } -}; - -export const onPromptState = (state: any) => { - if (state.aborted) { - // If we don't re-enable the terminal cursor before exiting - // the program, the cursor will remain hidden - process.stdout.write("\x1B[?25h"); - process.stdout.write("\n"); - process.exit(1); - } -}; - -export const askQuestions = async ( - program: QuestionArgs, - preferences: QuestionArgs, -) => { - const getPrefOrDefault = <K extends keyof QuestionArgs>( - field: K, - ): QuestionArgs[K] => preferences[field] ?? defaults[field]; - - // Ask for next action after installation - async function askPostInstallAction() { - if (program.postInstallAction === undefined) { - if (ciInfo.isCI) { - program.postInstallAction = getPrefOrDefault("postInstallAction"); - } else { - const actionChoices = [ - { - title: "Just generate code (~1 sec)", - value: "none", - }, - { - title: "Start in VSCode (~1 sec)", - value: "VSCode", - }, - { - title: "Generate code and install dependencies (~2 min)", - value: "dependencies", - }, - ]; - - const openAiKeyConfigured = - program.openAiKey || process.env["OPENAI_API_KEY"]; - // If using LlamaParse, require LlamaCloud API key - const llamaCloudKeyConfigured = ( - program.dataSource?.config as FileSourceConfig - )?.useLlamaParse - ? program.llamaCloudKey || process.env["LLAMA_CLOUD_API_KEY"] - : true; - const hasVectorDb = program.vectorDb && program.vectorDb !== "none"; - // Can run the app if all tools do not require configuration - if ( - !hasVectorDb && - openAiKeyConfigured && - llamaCloudKeyConfigured && - !toolsRequireConfig(program.tools) && - !program.llamapack - ) { - actionChoices.push({ - title: - "Generate code, install dependencies, and run the app (~2 min)", - value: "runApp", - }); - } - - const { action } = await prompts( - { - type: "select", - name: "action", - message: "How would you like to proceed?", - choices: actionChoices, - initial: 1, - }, - handlers, - ); - - program.postInstallAction = action; - } - } - } - - if (!program.template) { - if (ciInfo.isCI) { - program.template = getPrefOrDefault("template"); - } else { - const styledRepo = blue( - `https://github.com/${COMMUNITY_OWNER}/${COMMUNITY_REPO}`, - ); - const { template } = await prompts( - { - type: "select", - name: "template", - message: "Which template would you like to use?", - choices: [ - { title: "Chat without streaming", value: "simple" }, - { title: "Chat with streaming", value: "streaming" }, - { - title: `Community template from ${styledRepo}`, - value: "community", - }, - { - title: "Example using a LlamaPack", - value: "llamapack", - }, - ], - initial: 1, - }, - handlers, - ); - program.template = template; - preferences.template = template; - } - } - - if (program.template === "community") { - const rootFolderNames = await getRepoRootFolders( - COMMUNITY_OWNER, - COMMUNITY_REPO, - ); - const { communityProjectPath } = await prompts( - { - type: "select", - name: "communityProjectPath", - message: "Select community template", - choices: rootFolderNames.map((name) => ({ - title: name, - value: name, - })), - initial: 0, - }, - handlers, - ); - program.communityProjectPath = communityProjectPath; - preferences.communityProjectPath = communityProjectPath; - return; // early return - no further questions needed for community projects - } - - if (program.template === "llamapack") { - const availableLlamaPacks = await getAvailableLlamapackOptions(); - const { llamapack } = await prompts( - { - type: "select", - name: "llamapack", - message: "Select LlamaPack", - choices: availableLlamaPacks.map((pack) => ({ - title: pack.name, - value: pack.folderPath, - })), - initial: 0, - }, - handlers, - ); - program.llamapack = llamapack; - preferences.llamapack = llamapack; - await askPostInstallAction(); - return; // early return - no further questions needed for llamapack projects - } - - if (!program.framework) { - if (ciInfo.isCI) { - program.framework = getPrefOrDefault("framework"); - } else { - const choices = [ - { title: "Express", value: "express" }, - { title: "FastAPI (Python)", value: "fastapi" }, - ]; - if (program.template === "streaming") { - // allow NextJS only for streaming template - choices.unshift({ title: "NextJS", value: "nextjs" }); - } - - const { framework } = await prompts( - { - type: "select", - name: "framework", - message: "Which framework would you like to use?", - choices, - initial: 0, - }, - handlers, - ); - program.framework = framework; - preferences.framework = framework; - } - } - - if ( - program.template === "streaming" && - (program.framework === "express" || program.framework === "fastapi") - ) { - // if a backend-only framework is selected, ask whether we should create a frontend - // (only for streaming backends) - if (program.frontend === undefined) { - 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); - } - } - } else { - program.frontend = false; - } - - if (program.framework === "nextjs" || program.frontend) { - if (!program.ui) { - if (ciInfo.isCI) { - program.ui = getPrefOrDefault("ui"); - } else { - const { ui } = await prompts( - { - type: "select", - name: "ui", - message: "Which UI would you like to use?", - choices: [ - { title: "Just HTML", value: "html" }, - { title: "Shadcn", value: "shadcn" }, - ], - initial: 0, - }, - handlers, - ); - program.ui = ui; - preferences.ui = ui; - } - } - } - - if (!program.model) { - if (ciInfo.isCI) { - program.model = getPrefOrDefault("model"); - } else { - const { model } = await prompts( - { - type: "select", - name: "model", - message: "Which model would you like to use?", - choices: [ - { title: "gpt-3.5-turbo", value: "gpt-3.5-turbo-0125" }, - { title: "gpt-4-turbo-preview", value: "gpt-4-turbo-preview" }, - { title: "gpt-4", value: "gpt-4" }, - { - title: "gpt-4-vision-preview", - value: "gpt-4-vision-preview", - }, - ], - initial: 0, - }, - handlers, - ); - program.model = model; - preferences.model = model; - } - } - - if (!program.embeddingModel && program.framework === "fastapi") { - if (ciInfo.isCI) { - program.embeddingModel = getPrefOrDefault("embeddingModel"); - } else { - const { embeddingModel } = await prompts( - { - type: "select", - name: "embeddingModel", - message: "Which embedding model would you like to use?", - choices: [ - { - title: "text-embedding-ada-002", - value: "text-embedding-ada-002", - }, - { - title: "text-embedding-3-small", - value: "text-embedding-3-small", - }, - { - title: "text-embedding-3-large", - value: "text-embedding-3-large", - }, - ], - initial: 0, - }, - handlers, - ); - program.embeddingModel = embeddingModel; - preferences.embeddingModel = embeddingModel; - } - } - - if (program.files) { - // If user specified files option, then the program should use context engine - program.engine == "context"; - if (!fs.existsSync(program.files)) { - console.log("File or folder not found"); - process.exit(1); - } else { - program.dataSource = { - type: fs.lstatSync(program.files).isDirectory() ? "folder" : "file", - config: { - path: program.files, - }, - }; - } - } - - if (!program.engine) { - if (ciInfo.isCI) { - program.engine = getPrefOrDefault("engine"); - } else { - const { dataSource } = await prompts( - { - type: "select", - name: "dataSource", - message: "Which data source would you like to use?", - choices: getDataSourceChoices(program.framework), - initial: 1, - }, - handlers, - ); - // Initialize with default config - program.dataSource = getPrefOrDefault("dataSource"); - if (program.dataSource) { - switch (dataSource) { - case "simple": - program.engine = "simple"; - program.dataSource = { type: "none", config: {} }; - break; - case "exampleFile": - program.engine = "context"; - // Treat example as a folder data source with no config - program.dataSource = { type: "folder", config: {} }; - break; - case "localFile": - program.engine = "context"; - program.dataSource = { - type: "file", - config: { - path: await selectLocalContextData("file"), - }, - }; - break; - case "localFolder": - program.engine = "context"; - program.dataSource = { - type: "folder", - config: { - path: await selectLocalContextData("folder"), - }, - }; - break; - case "web": - program.engine = "context"; - program.dataSource.type = "web"; - break; - } - } - } - } else if (!program.dataSource) { - // Handle a case when engine is specified but dataSource is not - if (program.engine === "context") { - program.dataSource = { - type: "folder", - config: {}, - }; - } else if (program.engine === "simple") { - program.dataSource = { - type: "none", - config: {}, - }; - } - } - - if ( - (program.dataSource?.type === "file" || - program.dataSource?.type === "folder") && - program.framework === "fastapi" - ) { - if (ciInfo.isCI) { - program.llamaCloudKey = getPrefOrDefault("llamaCloudKey"); - } else { - const dataSourceConfig = program.dataSource.config as FileSourceConfig; - dataSourceConfig.useLlamaParse = program.llamaParse; - - // Is pdf file selected as data source or is it a folder data source - const askingLlamaParse = - dataSourceConfig.useLlamaParse === undefined && - (program.dataSource.type === "folder" - ? true - : dataSourceConfig.path && - path.extname(dataSourceConfig.path) === ".pdf"); - - // Ask if user wants to use LlamaParse - if (askingLlamaParse) { - const { useLlamaParse } = await prompts( - { - type: "toggle", - name: "useLlamaParse", - message: - "Would you like to use LlamaParse (improved parser for RAG - requires API key)?", - initial: true, - active: "yes", - inactive: "no", - }, - handlers, - ); - dataSourceConfig.useLlamaParse = useLlamaParse; - program.dataSource.config = dataSourceConfig; - } - - // Ask for LlamaCloud API key - if ( - dataSourceConfig.useLlamaParse && - program.llamaCloudKey === undefined - ) { - const { llamaCloudKey } = await prompts( - { - type: "text", - name: "llamaCloudKey", - message: - "Please provide your LlamaIndex Cloud API key (leave blank to skip):", - }, - handlers, - ); - program.llamaCloudKey = llamaCloudKey; - } - } - } - - if (program.dataSource?.type === "web" && program.framework === "fastapi") { - let { baseUrl } = await prompts( - { - type: "text", - name: "baseUrl", - message: "Please provide base URL of the website:", - initial: "https://www.llamaindex.ai", - }, - handlers, - ); - try { - if (!baseUrl.includes("://")) { - baseUrl = `https://${baseUrl}`; - } - const checkUrl = new URL(baseUrl); - if (checkUrl.protocol !== "https:" && checkUrl.protocol !== "http:") { - throw new Error("Invalid protocol"); - } - } catch (error) { - console.log( - red( - "Invalid URL provided! Please provide a valid URL (e.g. https://www.llamaindex.ai)", - ), - ); - process.exit(1); - } - program.dataSource.config = { - baseUrl: baseUrl, - depth: 1, - }; - } - - if (program.engine !== "simple" && !program.vectorDb) { - if (ciInfo.isCI) { - program.vectorDb = getPrefOrDefault("vectorDb"); - } else { - const { vectorDb } = await prompts( - { - type: "select", - name: "vectorDb", - message: "Would you like to use a vector database?", - choices: getVectorDbChoices(program.framework), - initial: 0, - }, - handlers, - ); - program.vectorDb = vectorDb; - preferences.vectorDb = vectorDb; - } - } - - if ( - !program.tools && - program.framework === "fastapi" && - program.engine === "context" - ) { - if (ciInfo.isCI) { - program.tools = getPrefOrDefault("tools"); - } else { - const toolChoices = supportedTools.map((tool) => ({ - title: tool.display, - value: tool.name, - })); - const { toolsName } = await prompts({ - type: "multiselect", - name: "toolsName", - message: - "Would you like to build an agent using tools? If so, select the tools here, otherwise just press enter", - choices: toolChoices, - }); - const tools = toolsName?.map((tool: string) => - supportedTools.find((t) => t.name === tool), - ); - program.tools = tools; - preferences.tools = tools; - } - } - - if (!program.openAiKey) { - const { key } = await prompts( - { - type: "text", - name: "key", - message: "Please provide your OpenAI API key (leave blank to skip):", - }, - handlers, - ); - program.openAiKey = key; - preferences.openAiKey = key; - } - - if (program.framework !== "fastapi" && program.eslint === undefined) { - if (ciInfo.isCI) { - program.eslint = getPrefOrDefault("eslint"); - } else { - const styledEslint = blue("ESLint"); - const { eslint } = await prompts({ - onState: onPromptState, - type: "toggle", - name: "eslint", - message: `Would you like to use ${styledEslint}?`, - initial: getPrefOrDefault("eslint"), - active: "Yes", - inactive: "No", - }); - program.eslint = Boolean(eslint); - preferences.eslint = Boolean(eslint); - } - } - - await askPostInstallAction(); - - // TODO: consider using zod to validate the input (doesn't work like this as not every option is required) - // templateUISchema.parse(program.ui); - // templateEngineSchema.parse(program.engine); - // templateFrameworkSchema.parse(program.framework); - // templateTypeSchema.parse(program.template);`` -}; diff --git a/packages/create-llama/templates/.gitignore b/packages/create-llama/templates/.gitignore deleted file mode 100644 index ec6c67b63..000000000 --- a/packages/create-llama/templates/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -__pycache__ -poetry.lock -storage diff --git a/packages/create-llama/templates/README-fullstack.md b/packages/create-llama/templates/README-fullstack.md deleted file mode 100644 index 5a41b8cfc..000000000 --- a/packages/create-llama/templates/README-fullstack.md +++ /dev/null @@ -1,18 +0,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! diff --git a/packages/create-llama/templates/components/data/101.pdf b/packages/create-llama/templates/components/data/101.pdf deleted file mode 100644 index ae5acffd5398b7c59e2df9e6dead2d99128b719c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47931 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 biTrLJK5l56Q)Cc`=qWO8ZY3?8OU(ZPHg=?$ diff --git a/packages/create-llama/templates/components/engines/python/agent/__init__.py b/packages/create-llama/templates/components/engines/python/agent/__init__.py deleted file mode 100644 index 52e363941..000000000 --- a/packages/create-llama/templates/components/engines/python/agent/__init__.py +++ /dev/null @@ -1,28 +0,0 @@ -import os -from llama_index.core.settings import Settings -from llama_index.core.agent import AgentRunner -from llama_index.core.tools.query_engine import QueryEngineTool -from app.engine.tools import ToolFactory -from app.engine.index import get_index - - -def get_chat_engine(): - system_prompt = os.getenv("SYSTEM_PROMPT") - top_k = os.getenv("TOP_K", "3") - tools = [] - - # Add query tool - index = get_index() - query_engine = index.as_query_engine(similarity_top_k=int(top_k)) - query_engine_tool = QueryEngineTool.from_defaults(query_engine=query_engine) - tools.append(query_engine_tool) - - # Add additional tools - tools += ToolFactory.from_env() - - return AgentRunner.from_llm( - llm=Settings.llm, - tools=tools, - system_prompt=system_prompt, - verbose=True, - ) diff --git a/packages/create-llama/templates/components/engines/python/agent/tools.py b/packages/create-llama/templates/components/engines/python/agent/tools.py deleted file mode 100644 index fafc1fcc8..000000000 --- a/packages/create-llama/templates/components/engines/python/agent/tools.py +++ /dev/null @@ -1,33 +0,0 @@ -import json -import importlib - -from llama_index.core.tools.tool_spec.base import BaseToolSpec -from llama_index.core.tools.function_tool import FunctionTool - - -class ToolFactory: - - @staticmethod - def create_tool(tool_name: str, **kwargs) -> list[FunctionTool]: - try: - tool_package, tool_cls_name = tool_name.split(".") - module_name = f"llama_index.tools.{tool_package}" - module = importlib.import_module(module_name) - tool_class = getattr(module, tool_cls_name) - tool_spec: BaseToolSpec = tool_class(**kwargs) - return tool_spec.to_tool_list() - except (ImportError, AttributeError) as e: - raise ValueError(f"Unsupported tool: {tool_name}") from e - except TypeError as e: - raise ValueError( - f"Could not create tool: {tool_name}. With config: {kwargs}" - ) from e - - @staticmethod - def from_env() -> list[FunctionTool]: - tools = [] - with open("tools_config.json", "r") as f: - tool_configs = json.load(f) - for name, config in tool_configs.items(): - tools += ToolFactory.create_tool(name, **config) - return tools diff --git a/packages/create-llama/templates/components/engines/python/chat/__init__.py b/packages/create-llama/templates/components/engines/python/chat/__init__.py deleted file mode 100644 index 65624c32c..000000000 --- a/packages/create-llama/templates/components/engines/python/chat/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -import os -from app.engine.index import get_index - - -def get_chat_engine(): - system_prompt = os.getenv("SYSTEM_PROMPT") - top_k = os.getenv("TOP_K", 3) - - return get_index().as_chat_engine( - similarity_top_k=int(top_k), - system_prompt=system_prompt, - chat_mode="condense_plus_context", - ) diff --git a/packages/create-llama/templates/components/loaders/python/file/loader.py b/packages/create-llama/templates/components/loaders/python/file/loader.py deleted file mode 100644 index c1178444e..000000000 --- a/packages/create-llama/templates/components/loaders/python/file/loader.py +++ /dev/null @@ -1,7 +0,0 @@ -from llama_index.core.readers import SimpleDirectoryReader - -DATA_DIR = "data" # directory containing the documents - - -def get_documents(): - return SimpleDirectoryReader(DATA_DIR).load_data() diff --git a/packages/create-llama/templates/components/loaders/python/llama_parse/loader.py b/packages/create-llama/templates/components/loaders/python/llama_parse/loader.py deleted file mode 100644 index 4c9fbf109..000000000 --- a/packages/create-llama/templates/components/loaders/python/llama_parse/loader.py +++ /dev/null @@ -1,17 +0,0 @@ -import os -from llama_parse import LlamaParse -from llama_index.core import SimpleDirectoryReader - -DATA_DIR = "data" # directory containing the documents - - -def get_documents(): - if os.getenv("LLAMA_CLOUD_API_KEY") is None: - raise ValueError( - "LLAMA_CLOUD_API_KEY environment variable is not set. " - "Please set it in .env file or in your shell environment then run again!" - ) - parser = LlamaParse(result_type="markdown", verbose=True, language="en") - - reader = SimpleDirectoryReader(DATA_DIR, file_extractor={".pdf": parser}) - return reader.load_data() diff --git a/packages/create-llama/templates/components/loaders/python/web/loader.py b/packages/create-llama/templates/components/loaders/python/web/loader.py deleted file mode 100644 index bc6d0496d..000000000 --- a/packages/create-llama/templates/components/loaders/python/web/loader.py +++ /dev/null @@ -1,13 +0,0 @@ -import os -from llama_index.readers.web import WholeSiteReader - - -def get_documents(): - # Initialize the scraper with a prefix URL and maximum depth - scraper = WholeSiteReader( - prefix=os.environ.get("URL_PREFIX"), max_depth=int(os.environ.get("MAX_DEPTH")) - ) - # Start scraping from a base URL - documents = scraper.load_data(base_url=os.environ.get("BASE_URL")) - - return documents diff --git a/packages/create-llama/templates/components/sample-projects/llamapack/README-template.md b/packages/create-llama/templates/components/sample-projects/llamapack/README-template.md deleted file mode 100644 index f669c38c2..000000000 --- a/packages/create-llama/templates/components/sample-projects/llamapack/README-template.md +++ /dev/null @@ -1,16 +0,0 @@ ---- - -## Quickstart - -1. Check above instructions for setting up your environment and export required environment variables - For example, if you are using bash, you can run the following command to set up OpenAI API key - -```bash -export OPENAI_API_KEY=your_api_key -``` - -2. Run the example - -``` -poetry run python example.py -``` diff --git a/packages/create-llama/templates/components/sample-projects/llamapack/pyproject.toml b/packages/create-llama/templates/components/sample-projects/llamapack/pyproject.toml deleted file mode 100644 index 4bd28bd80..000000000 --- a/packages/create-llama/templates/components/sample-projects/llamapack/pyproject.toml +++ /dev/null @@ -1,17 +0,0 @@ -[tool.poetry] -name = "app" -version = "0.1.0" -description = "Llama Pack Example" -authors = ["Marcus Schiesser <mail@marcusschiesser.de>"] -readme = "README.md" - -[tool.poetry.dependencies] -python = "^3.11,<3.12" -llama-index = "^0.10.6" -llama-index-readers-file = "^0.1.3" -python-dotenv = "^1.0.0" - - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" diff --git a/packages/create-llama/templates/components/ui/html/chat/chat-avatar.tsx b/packages/create-llama/templates/components/ui/html/chat/chat-avatar.tsx deleted file mode 100644 index cd241104e..000000000 --- a/packages/create-llama/templates/components/ui/html/chat/chat-avatar.tsx +++ /dev/null @@ -1,34 +0,0 @@ -"use client"; - -import Image from "next/image"; -import { Message } from "./chat-messages"; - -export default function ChatAvatar(message: Message) { - if (message.role === "user") { - return ( - <div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border shadow bg-background"> - <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 256 256" - fill="currentColor" - className="h-4 w-4" - > - <path d="M230.92 212c-15.23-26.33-38.7-45.21-66.09-54.16a72 72 0 1 0-73.66 0c-27.39 8.94-50.86 27.82-66.09 54.16a8 8 0 1 0 13.85 8c18.84-32.56 52.14-52 89.07-52s70.23 19.44 89.07 52a8 8 0 1 0 13.85-8ZM72 96a56 56 0 1 1 56 56 56.06 56.06 0 0 1-56-56Z"></path> - </svg> - </div> - ); - } - - return ( - <div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border bg-black text-white"> - <Image - className="rounded-md" - src="/llama.png" - alt="Llama Logo" - width={24} - height={24} - priority - /> - </div> - ); -} diff --git a/packages/create-llama/templates/components/ui/html/chat/chat-input.tsx b/packages/create-llama/templates/components/ui/html/chat/chat-input.tsx deleted file mode 100644 index 7c3e87280..000000000 --- a/packages/create-llama/templates/components/ui/html/chat/chat-input.tsx +++ /dev/null @@ -1,43 +0,0 @@ -"use client"; - -export interface ChatInputProps { - /** The current value of the input */ - input?: string; - /** An input/textarea-ready onChange handler to control the value of the input */ - handleInputChange?: ( - e: - | React.ChangeEvent<HTMLInputElement> - | React.ChangeEvent<HTMLTextAreaElement>, - ) => void; - /** Form submission handler to automatically reset input and append a user message */ - handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void; - isLoading: boolean; - multiModal?: boolean; -} - -export default function ChatInput(props: ChatInputProps) { - return ( - <> - <form - onSubmit={props.handleSubmit} - className="flex items-start justify-between w-full max-w-5xl p-4 bg-white rounded-xl shadow-xl gap-4" - > - <input - autoFocus - name="message" - placeholder="Type a message" - className="w-full p-4 rounded-xl shadow-inner flex-1" - value={props.input} - onChange={props.handleInputChange} - /> - <button - disabled={props.isLoading} - type="submit" - className="p-4 text-white rounded-xl shadow-xl bg-gradient-to-r from-cyan-500 to-sky-500 disabled:opacity-50 disabled:cursor-not-allowed" - > - Send message - </button> - </form> - </> - ); -} diff --git a/packages/create-llama/templates/components/ui/html/chat/chat-item.tsx b/packages/create-llama/templates/components/ui/html/chat/chat-item.tsx deleted file mode 100644 index 2244f729a..000000000 --- a/packages/create-llama/templates/components/ui/html/chat/chat-item.tsx +++ /dev/null @@ -1,13 +0,0 @@ -"use client"; - -import ChatAvatar from "./chat-avatar"; -import { Message } from "./chat-messages"; - -export default function ChatItem(message: Message) { - return ( - <div className="flex items-start gap-4 pt-5"> - <ChatAvatar {...message} /> - <p className="break-words">{message.content}</p> - </div> - ); -} diff --git a/packages/create-llama/templates/components/ui/html/chat/chat-messages.tsx b/packages/create-llama/templates/components/ui/html/chat/chat-messages.tsx deleted file mode 100644 index 0e9783940..000000000 --- a/packages/create-llama/templates/components/ui/html/chat/chat-messages.tsx +++ /dev/null @@ -1,48 +0,0 @@ -"use client"; - -import { useEffect, useRef } from "react"; -import ChatItem from "./chat-item"; - -export interface Message { - id: string; - content: string; - role: string; -} - -export default function ChatMessages({ - messages, - isLoading, - reload, - stop, -}: { - messages: Message[]; - isLoading?: boolean; - stop?: () => void; - reload?: () => void; -}) { - const scrollableChatContainerRef = useRef<HTMLDivElement>(null); - - const scrollToBottom = () => { - if (scrollableChatContainerRef.current) { - scrollableChatContainerRef.current.scrollTop = - scrollableChatContainerRef.current.scrollHeight; - } - }; - - useEffect(() => { - scrollToBottom(); - }, [messages.length]); - - return ( - <div className="w-full max-w-5xl p-4 bg-white rounded-xl shadow-xl"> - <div - className="flex flex-col gap-5 divide-y h-[50vh] overflow-auto" - ref={scrollableChatContainerRef} - > - {messages.map((m: Message) => ( - <ChatItem key={m.id} {...m} /> - ))} - </div> - </div> - ); -} diff --git a/packages/create-llama/templates/components/ui/html/chat/index.ts b/packages/create-llama/templates/components/ui/html/chat/index.ts deleted file mode 100644 index 5de7dce47..000000000 --- a/packages/create-llama/templates/components/ui/html/chat/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import ChatInput from "./chat-input"; -import ChatMessages from "./chat-messages"; - -export type { ChatInputProps } from "./chat-input"; -export type { Message } from "./chat-messages"; -export { ChatInput, ChatMessages }; diff --git a/packages/create-llama/templates/components/vectordbs/python/mongo/__init__.py b/packages/create-llama/templates/components/vectordbs/python/mongo/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/create-llama/templates/components/vectordbs/python/mongo/generate.py b/packages/create-llama/templates/components/vectordbs/python/mongo/generate.py deleted file mode 100644 index 69d520711..000000000 --- a/packages/create-llama/templates/components/vectordbs/python/mongo/generate.py +++ /dev/null @@ -1,43 +0,0 @@ -from dotenv import load_dotenv - -load_dotenv() - -import os -import logging -from llama_index.core.storage import StorageContext -from llama_index.core.indices import VectorStoreIndex -from llama_index.vector_stores.mongodb import MongoDBAtlasVectorSearch -from app.settings import init_settings -from app.engine.loader import get_documents - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger() - - -def generate_datasource(): - logger.info("Creating new index") - # load the documents and create the index - documents = get_documents() - store = MongoDBAtlasVectorSearch( - db_name=os.environ["MONGODB_DATABASE"], - collection_name=os.environ["MONGODB_VECTORS"], - index_name=os.environ["MONGODB_VECTOR_INDEX"], - ) - storage_context = StorageContext.from_defaults(vector_store=store) - VectorStoreIndex.from_documents( - documents, - storage_context=storage_context, - show_progress=True, # this will show you a progress bar as the embeddings are created - ) - logger.info( - f"Successfully created embeddings in the MongoDB collection {os.environ['MONGODB_VECTORS']}" - ) - logger.info( - """IMPORTANT: You can't query your index yet because you need to create a vector search index in MongoDB's UI now. -See https://github.com/run-llama/mongodb-demo/tree/main?tab=readme-ov-file#create-a-vector-search-index""" - ) - - -if __name__ == "__main__": - init_settings() - generate_datasource() diff --git a/packages/create-llama/templates/components/vectordbs/python/mongo/index.py b/packages/create-llama/templates/components/vectordbs/python/mongo/index.py deleted file mode 100644 index 6dba7c1d0..000000000 --- a/packages/create-llama/templates/components/vectordbs/python/mongo/index.py +++ /dev/null @@ -1,20 +0,0 @@ -import logging -import os - -from llama_index.core.indices import VectorStoreIndex -from llama_index.vector_stores.mongodb import MongoDBAtlasVectorSearch - - -logger = logging.getLogger("uvicorn") - - -def get_index(): - logger.info("Connecting to index from MongoDB...") - store = MongoDBAtlasVectorSearch( - db_name=os.environ["MONGODB_DATABASE"], - collection_name=os.environ["MONGODB_VECTORS"], - index_name=os.environ["MONGODB_VECTOR_INDEX"], - ) - index = VectorStoreIndex.from_vector_store(store) - logger.info("Finished connecting to index from MongoDB.") - return index diff --git a/packages/create-llama/templates/components/vectordbs/python/none/__init__.py b/packages/create-llama/templates/components/vectordbs/python/none/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/create-llama/templates/components/vectordbs/python/none/constants.py b/packages/create-llama/templates/components/vectordbs/python/none/constants.py deleted file mode 100644 index 254998ebb..000000000 --- a/packages/create-llama/templates/components/vectordbs/python/none/constants.py +++ /dev/null @@ -1 +0,0 @@ -STORAGE_DIR = "storage" # directory to cache the generated index diff --git a/packages/create-llama/templates/components/vectordbs/python/none/generate.py b/packages/create-llama/templates/components/vectordbs/python/none/generate.py deleted file mode 100644 index 3c8055f37..000000000 --- a/packages/create-llama/templates/components/vectordbs/python/none/generate.py +++ /dev/null @@ -1,32 +0,0 @@ -from dotenv import load_dotenv - -load_dotenv() - -import logging -from llama_index.core.indices import ( - VectorStoreIndex, -) -from app.engine.constants import STORAGE_DIR -from app.engine.loader import get_documents -from app.settings import init_settings - - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger() - - -def generate_datasource(): - logger.info("Creating new index") - # load the documents and create the index - documents = get_documents() - index = VectorStoreIndex.from_documents( - documents, - ) - # store it for later - index.storage_context.persist(STORAGE_DIR) - logger.info(f"Finished creating new index. Stored in {STORAGE_DIR}") - - -if __name__ == "__main__": - init_settings() - generate_datasource() diff --git a/packages/create-llama/templates/components/vectordbs/python/none/index.py b/packages/create-llama/templates/components/vectordbs/python/none/index.py deleted file mode 100644 index 4dcc858a0..000000000 --- a/packages/create-llama/templates/components/vectordbs/python/none/index.py +++ /dev/null @@ -1,23 +0,0 @@ -import logging -import os - -from app.engine.constants import STORAGE_DIR -from llama_index.core.storage import StorageContext -from llama_index.core.indices import load_index_from_storage - -logger = logging.getLogger("uvicorn") - - -def get_index(): - # check if storage already exists - if not os.path.exists(STORAGE_DIR): - raise Exception( - "StorageContext is empty - call 'python app/engine/generate.py' to generate the storage first" - ) - - # load the existing index - logger.info(f"Loading index from {STORAGE_DIR}...") - storage_context = StorageContext.from_defaults(persist_dir=STORAGE_DIR) - index = load_index_from_storage(storage_context) - logger.info(f"Finished loading index from {STORAGE_DIR}") - return index diff --git a/packages/create-llama/templates/components/vectordbs/python/pg/__init__.py b/packages/create-llama/templates/components/vectordbs/python/pg/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/create-llama/templates/components/vectordbs/python/pg/constants.py b/packages/create-llama/templates/components/vectordbs/python/pg/constants.py deleted file mode 100644 index a4ebd9183..000000000 --- a/packages/create-llama/templates/components/vectordbs/python/pg/constants.py +++ /dev/null @@ -1,2 +0,0 @@ -PGVECTOR_SCHEMA = "public" -PGVECTOR_TABLE = "llamaindex_embedding" \ No newline at end of file diff --git a/packages/create-llama/templates/components/vectordbs/python/pg/generate.py b/packages/create-llama/templates/components/vectordbs/python/pg/generate.py deleted file mode 100644 index 608beb2e8..000000000 --- a/packages/create-llama/templates/components/vectordbs/python/pg/generate.py +++ /dev/null @@ -1,35 +0,0 @@ -from dotenv import load_dotenv - -load_dotenv() - -import logging -from llama_index.core.indices import VectorStoreIndex -from llama_index.core.storage import StorageContext - -from app.engine.loader import get_documents -from app.settings import init_settings -from app.engine.utils import init_pg_vector_store_from_env - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger() - - -def generate_datasource(): - logger.info("Creating new index") - # load the documents and create the index - documents = get_documents() - store = init_pg_vector_store_from_env() - storage_context = StorageContext.from_defaults(vector_store=store) - VectorStoreIndex.from_documents( - documents, - storage_context=storage_context, - show_progress=True, # this will show you a progress bar as the embeddings are created - ) - logger.info( - f"Successfully created embeddings in the PG vector store, schema={store.schema_name} table={store.table_name}" - ) - - -if __name__ == "__main__": - init_settings() - generate_datasource() diff --git a/packages/create-llama/templates/components/vectordbs/python/pg/index.py b/packages/create-llama/templates/components/vectordbs/python/pg/index.py deleted file mode 100644 index 3c4f31800..000000000 --- a/packages/create-llama/templates/components/vectordbs/python/pg/index.py +++ /dev/null @@ -1,13 +0,0 @@ -import logging -from llama_index.core.indices.vector_store import VectorStoreIndex -from app.engine.utils import init_pg_vector_store_from_env - -logger = logging.getLogger("uvicorn") - - -def get_index(): - logger.info("Connecting to index from PGVector...") - store = init_pg_vector_store_from_env() - index = VectorStoreIndex.from_vector_store(store) - logger.info("Finished connecting to index from PGVector.") - return index diff --git a/packages/create-llama/templates/components/vectordbs/python/pg/utils.py b/packages/create-llama/templates/components/vectordbs/python/pg/utils.py deleted file mode 100644 index 39127846d..000000000 --- a/packages/create-llama/templates/components/vectordbs/python/pg/utils.py +++ /dev/null @@ -1,27 +0,0 @@ -import os -from llama_index.vector_stores.postgres import PGVectorStore -from urllib.parse import urlparse -from app.engine.constants import PGVECTOR_SCHEMA, PGVECTOR_TABLE - - -def init_pg_vector_store_from_env(): - original_conn_string = os.environ.get("PG_CONNECTION_STRING") - if original_conn_string is None or original_conn_string == "": - raise ValueError("PG_CONNECTION_STRING environment variable is not set.") - - # The PGVectorStore requires both two connection strings, one for psycopg2 and one for asyncpg - # Update the configured scheme with the psycopg2 and asyncpg schemes - original_scheme = urlparse(original_conn_string).scheme + "://" - conn_string = original_conn_string.replace( - original_scheme, "postgresql+psycopg2://" - ) - async_conn_string = original_conn_string.replace( - original_scheme, "postgresql+asyncpg://" - ) - - return PGVectorStore( - connection_string=conn_string, - async_connection_string=async_conn_string, - schema_name=PGVECTOR_SCHEMA, - table_name=PGVECTOR_TABLE, - ) diff --git a/packages/create-llama/templates/components/vectordbs/python/pinecone/__init__.py b/packages/create-llama/templates/components/vectordbs/python/pinecone/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/create-llama/templates/components/vectordbs/python/pinecone/generate.py b/packages/create-llama/templates/components/vectordbs/python/pinecone/generate.py deleted file mode 100644 index 4e14648b0..000000000 --- a/packages/create-llama/templates/components/vectordbs/python/pinecone/generate.py +++ /dev/null @@ -1,39 +0,0 @@ -from dotenv import load_dotenv - -load_dotenv() - -import os -import logging -from llama_index.core.storage import StorageContext -from llama_index.core.indices import VectorStoreIndex -from llama_index.vector_stores.pinecone import PineconeVectorStore -from app.settings import init_settings -from app.engine.loader import get_documents - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger() - - -def generate_datasource(): - logger.info("Creating new index") - # load the documents and create the index - documents = get_documents() - store = PineconeVectorStore( - api_key=os.environ["PINECONE_API_KEY"], - index_name=os.environ["PINECONE_INDEX_NAME"], - environment=os.environ["PINECONE_ENVIRONMENT"], - ) - storage_context = StorageContext.from_defaults(vector_store=store) - VectorStoreIndex.from_documents( - documents, - storage_context=storage_context, - show_progress=True, # this will show you a progress bar as the embeddings are created - ) - logger.info( - f"Successfully created embeddings and save to your Pinecone index {os.environ['PINECONE_INDEX_NAME']}" - ) - - -if __name__ == "__main__": - init_settings() - generate_datasource() diff --git a/packages/create-llama/templates/components/vectordbs/python/pinecone/index.py b/packages/create-llama/templates/components/vectordbs/python/pinecone/index.py deleted file mode 100644 index 98824ffdc..000000000 --- a/packages/create-llama/templates/components/vectordbs/python/pinecone/index.py +++ /dev/null @@ -1,20 +0,0 @@ -import logging -import os - -from llama_index.core.indices import VectorStoreIndex -from llama_index.vector_stores.pinecone import PineconeVectorStore - - -logger = logging.getLogger("uvicorn") - - -def get_index(): - logger.info("Connecting to index from Pinecone...") - store = PineconeVectorStore( - api_key=os.environ["PINECONE_API_KEY"], - index_name=os.environ["PINECONE_INDEX_NAME"], - environment=os.environ["PINECONE_ENVIRONMENT"], - ) - index = VectorStoreIndex.from_vector_store(store) - logger.info("Finished connecting to index from Pinecone.") - return index diff --git a/packages/create-llama/templates/components/vectordbs/typescript/mongo/generate.mjs b/packages/create-llama/templates/components/vectordbs/typescript/mongo/generate.mjs deleted file mode 100644 index 7337d1222..000000000 --- a/packages/create-llama/templates/components/vectordbs/typescript/mongo/generate.mjs +++ /dev/null @@ -1,49 +0,0 @@ -/* eslint-disable turbo/no-undeclared-env-vars */ -import * as dotenv from "dotenv"; -import { - MongoDBAtlasVectorSearch, - SimpleDirectoryReader, - VectorStoreIndex, - storageContextFromDefaults, -} from "llamaindex"; -import { MongoClient } from "mongodb"; -import { STORAGE_DIR, checkRequiredEnvVars } from "./shared.mjs"; - -dotenv.config(); - -const mongoUri = process.env.MONGO_URI; -const databaseName = process.env.MONGODB_DATABASE; -const vectorCollectionName = process.env.MONGODB_VECTORS; -const indexName = process.env.MONGODB_VECTOR_INDEX; - -async function loadAndIndex() { - // Create a new client and connect to the server - const client = new MongoClient(mongoUri); - - // load objects from storage and convert them into LlamaIndex Document objects - const documents = await new SimpleDirectoryReader().loadData({ - directoryPath: STORAGE_DIR, - }); - - // create Atlas as a vector store - const vectorStore = new MongoDBAtlasVectorSearch({ - mongodbClient: client, - dbName: databaseName, - collectionName: vectorCollectionName, // this is where your embeddings will be stored - indexName: indexName, // this is the name of the index you will need to create - }); - - // now create an index from all the Documents and store them in Atlas - const storageContext = await storageContextFromDefaults({ vectorStore }); - await VectorStoreIndex.fromDocuments(documents, { storageContext }); - console.log( - `Successfully created embeddings in the MongoDB collection ${vectorCollectionName}.`, - ); - await client.close(); -} - -(async () => { - checkRequiredEnvVars(); - await loadAndIndex(); - console.log("Finished generating storage."); -})(); diff --git a/packages/create-llama/templates/components/vectordbs/typescript/mongo/index.ts b/packages/create-llama/templates/components/vectordbs/typescript/mongo/index.ts deleted file mode 100644 index 844789c60..000000000 --- a/packages/create-llama/templates/components/vectordbs/typescript/mongo/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* eslint-disable turbo/no-undeclared-env-vars */ -import { - ContextChatEngine, - LLM, - MongoDBAtlasVectorSearch, - serviceContextFromDefaults, - VectorStoreIndex, -} from "llamaindex"; -import { MongoClient } from "mongodb"; -import { checkRequiredEnvVars, CHUNK_OVERLAP, CHUNK_SIZE } from "./shared.mjs"; - -async function getDataSource(llm: LLM) { - checkRequiredEnvVars(); - const client = new MongoClient(process.env.MONGO_URI!); - const serviceContext = serviceContextFromDefaults({ - llm, - chunkSize: CHUNK_SIZE, - chunkOverlap: CHUNK_OVERLAP, - }); - const store = new MongoDBAtlasVectorSearch({ - mongodbClient: client, - dbName: process.env.MONGODB_DATABASE, - collectionName: process.env.MONGODB_VECTORS, - indexName: process.env.MONGODB_VECTOR_INDEX, - }); - - return await VectorStoreIndex.fromVectorStore(store, serviceContext); -} - -export async function createChatEngine(llm: LLM) { - const index = await getDataSource(llm); - const retriever = index.asRetriever({ similarityTopK: 3 }); - return new ContextChatEngine({ - chatModel: llm, - retriever, - }); -} diff --git a/packages/create-llama/templates/components/vectordbs/typescript/mongo/shared.mjs b/packages/create-llama/templates/components/vectordbs/typescript/mongo/shared.mjs deleted file mode 100644 index 264a82f06..000000000 --- a/packages/create-llama/templates/components/vectordbs/typescript/mongo/shared.mjs +++ /dev/null @@ -1,27 +0,0 @@ -export const STORAGE_DIR = "./data"; -export const CHUNK_SIZE = 512; -export const CHUNK_OVERLAP = 20; - -const REQUIRED_ENV_VARS = [ - "MONGO_URI", - "MONGODB_DATABASE", - "MONGODB_VECTORS", - "MONGODB_VECTOR_INDEX", -]; - -export function checkRequiredEnvVars() { - const missingEnvVars = REQUIRED_ENV_VARS.filter((envVar) => { - return !process.env[envVar]; - }); - - if (missingEnvVars.length > 0) { - console.log( - `The following environment variables are required but missing: ${missingEnvVars.join( - ", ", - )}`, - ); - throw new Error( - `Missing environment variables: ${missingEnvVars.join(", ")}`, - ); - } -} diff --git a/packages/create-llama/templates/components/vectordbs/typescript/none/constants.mjs b/packages/create-llama/templates/components/vectordbs/typescript/none/constants.mjs deleted file mode 100644 index 8cfb403c3..000000000 --- a/packages/create-llama/templates/components/vectordbs/typescript/none/constants.mjs +++ /dev/null @@ -1,4 +0,0 @@ -export const STORAGE_DIR = "./data"; -export const STORAGE_CACHE_DIR = "./cache"; -export const CHUNK_SIZE = 512; -export const CHUNK_OVERLAP = 20; diff --git a/packages/create-llama/templates/components/vectordbs/typescript/none/generate.mjs b/packages/create-llama/templates/components/vectordbs/typescript/none/generate.mjs deleted file mode 100644 index 9334f98e4..000000000 --- a/packages/create-llama/templates/components/vectordbs/typescript/none/generate.mjs +++ /dev/null @@ -1,53 +0,0 @@ -import { - serviceContextFromDefaults, - SimpleDirectoryReader, - storageContextFromDefaults, - VectorStoreIndex, -} from "llamaindex"; - -import * as dotenv from "dotenv"; - -import { - CHUNK_OVERLAP, - CHUNK_SIZE, - STORAGE_CACHE_DIR, - STORAGE_DIR, -} from "./constants.mjs"; - -// Load environment variables from local .env file -dotenv.config(); - -async function getRuntime(func) { - const start = Date.now(); - await func(); - const end = Date.now(); - return end - start; -} - -async function generateDatasource(serviceContext) { - console.log(`Generating storage context...`); - // Split documents, create embeddings and store them in the storage context - const ms = await getRuntime(async () => { - const storageContext = await storageContextFromDefaults({ - persistDir: STORAGE_CACHE_DIR, - }); - const documents = await new SimpleDirectoryReader().loadData({ - directoryPath: STORAGE_DIR, - }); - await VectorStoreIndex.fromDocuments(documents, { - storageContext, - serviceContext, - }); - }); - console.log(`Storage context successfully generated in ${ms / 1000}s.`); -} - -(async () => { - const serviceContext = serviceContextFromDefaults({ - chunkSize: CHUNK_SIZE, - chunkOverlap: CHUNK_OVERLAP, - }); - - await generateDatasource(serviceContext); - console.log("Finished generating storage."); -})(); diff --git a/packages/create-llama/templates/components/vectordbs/typescript/none/index.ts b/packages/create-llama/templates/components/vectordbs/typescript/none/index.ts deleted file mode 100644 index e335446cf..000000000 --- a/packages/create-llama/templates/components/vectordbs/typescript/none/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { - ContextChatEngine, - LLM, - serviceContextFromDefaults, - SimpleDocumentStore, - storageContextFromDefaults, - VectorStoreIndex, -} from "llamaindex"; -import { CHUNK_OVERLAP, CHUNK_SIZE, STORAGE_CACHE_DIR } from "./constants.mjs"; - -async function getDataSource(llm: LLM) { - const serviceContext = serviceContextFromDefaults({ - llm, - chunkSize: CHUNK_SIZE, - chunkOverlap: CHUNK_OVERLAP, - }); - const storageContext = await storageContextFromDefaults({ - persistDir: `${STORAGE_CACHE_DIR}`, - }); - - const numberOfDocs = Object.keys( - (storageContext.docStore as SimpleDocumentStore).toDict(), - ).length; - if (numberOfDocs === 0) { - throw new Error( - `StorageContext is empty - call 'npm run generate' to generate the storage first`, - ); - } - return await VectorStoreIndex.init({ - storageContext, - serviceContext, - }); -} - -export async function createChatEngine(llm: LLM) { - const index = await getDataSource(llm); - const retriever = index.asRetriever(); - retriever.similarityTopK = 3; - - return new ContextChatEngine({ - chatModel: llm, - retriever, - }); -} diff --git a/packages/create-llama/templates/components/vectordbs/typescript/pg/generate.mjs b/packages/create-llama/templates/components/vectordbs/typescript/pg/generate.mjs deleted file mode 100644 index 3d959c698..000000000 --- a/packages/create-llama/templates/components/vectordbs/typescript/pg/generate.mjs +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-disable turbo/no-undeclared-env-vars */ -import * as dotenv from "dotenv"; -import { - PGVectorStore, - SimpleDirectoryReader, - VectorStoreIndex, - storageContextFromDefaults, -} from "llamaindex"; -import { - PGVECTOR_SCHEMA, - PGVECTOR_TABLE, - STORAGE_DIR, - checkRequiredEnvVars, -} from "./shared.mjs"; - -dotenv.config(); - -async function loadAndIndex() { - // load objects from storage and convert them into LlamaIndex Document objects - const documents = await new SimpleDirectoryReader().loadData({ - directoryPath: STORAGE_DIR, - }); - - // create postgres vector store - const vectorStore = new PGVectorStore({ - connectionString: process.env.PG_CONNECTION_STRING, - schemaName: PGVECTOR_SCHEMA, - tableName: PGVECTOR_TABLE, - }); - vectorStore.setCollection(STORAGE_DIR); - vectorStore.clearCollection(); - - // create index from all the Documents - console.log("Start creating embeddings..."); - const storageContext = await storageContextFromDefaults({ vectorStore }); - await VectorStoreIndex.fromDocuments(documents, { storageContext }); - console.log(`Successfully created embeddings.`); -} - -(async () => { - checkRequiredEnvVars(); - await loadAndIndex(); - console.log("Finished generating storage."); - process.exit(0); -})(); diff --git a/packages/create-llama/templates/components/vectordbs/typescript/pg/index.ts b/packages/create-llama/templates/components/vectordbs/typescript/pg/index.ts deleted file mode 100644 index 7de66a2e3..000000000 --- a/packages/create-llama/templates/components/vectordbs/typescript/pg/index.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* eslint-disable turbo/no-undeclared-env-vars */ -import { - ContextChatEngine, - LLM, - PGVectorStore, - VectorStoreIndex, - serviceContextFromDefaults, -} from "llamaindex"; -import { - CHUNK_OVERLAP, - CHUNK_SIZE, - PGVECTOR_SCHEMA, - PGVECTOR_TABLE, - checkRequiredEnvVars, -} from "./shared.mjs"; - -async function getDataSource(llm: LLM) { - checkRequiredEnvVars(); - const pgvs = new PGVectorStore({ - connectionString: process.env.PG_CONNECTION_STRING, - schemaName: PGVECTOR_SCHEMA, - tableName: PGVECTOR_TABLE, - }); - const serviceContext = serviceContextFromDefaults({ - llm, - chunkSize: CHUNK_SIZE, - chunkOverlap: CHUNK_OVERLAP, - }); - return await VectorStoreIndex.fromVectorStore(pgvs, serviceContext); -} - -export async function createChatEngine(llm: LLM) { - const index = await getDataSource(llm); - const retriever = index.asRetriever({ similarityTopK: 3 }); - return new ContextChatEngine({ - chatModel: llm, - retriever, - }); -} diff --git a/packages/create-llama/templates/components/vectordbs/typescript/pg/shared.mjs b/packages/create-llama/templates/components/vectordbs/typescript/pg/shared.mjs deleted file mode 100644 index 8ad729c0a..000000000 --- a/packages/create-llama/templates/components/vectordbs/typescript/pg/shared.mjs +++ /dev/null @@ -1,24 +0,0 @@ -export const STORAGE_DIR = "./data"; -export const CHUNK_SIZE = 512; -export const CHUNK_OVERLAP = 20; -export const PGVECTOR_SCHEMA = "public"; -export const PGVECTOR_TABLE = "llamaindex_embedding"; - -const REQUIRED_ENV_VARS = ["PG_CONNECTION_STRING", "OPENAI_API_KEY"]; - -export function checkRequiredEnvVars() { - const missingEnvVars = REQUIRED_ENV_VARS.filter((envVar) => { - return !process.env[envVar]; - }); - - if (missingEnvVars.length > 0) { - console.log( - `The following environment variables are required but missing: ${missingEnvVars.join( - ", ", - )}`, - ); - throw new Error( - `Missing environment variables: ${missingEnvVars.join(", ")}`, - ); - } -} diff --git a/packages/create-llama/templates/components/vectordbs/typescript/pinecone/generate.mjs b/packages/create-llama/templates/components/vectordbs/typescript/pinecone/generate.mjs deleted file mode 100644 index b371a639a..000000000 --- a/packages/create-llama/templates/components/vectordbs/typescript/pinecone/generate.mjs +++ /dev/null @@ -1,35 +0,0 @@ -/* eslint-disable turbo/no-undeclared-env-vars */ -import * as dotenv from "dotenv"; -import { - PineconeVectorStore, - SimpleDirectoryReader, - VectorStoreIndex, - storageContextFromDefaults, -} from "llamaindex"; -import { STORAGE_DIR, checkRequiredEnvVars } from "./shared.mjs"; - -dotenv.config(); - -async function loadAndIndex() { - // load objects from storage and convert them into LlamaIndex Document objects - const documents = await new SimpleDirectoryReader().loadData({ - directoryPath: STORAGE_DIR, - }); - - // create vector store - const vectorStore = new PineconeVectorStore(); - - // create index from all the Documentss and store them in Pinecone - console.log("Start creating embeddings..."); - const storageContext = await storageContextFromDefaults({ vectorStore }); - await VectorStoreIndex.fromDocuments(documents, { storageContext }); - console.log( - "Successfully created embeddings and save to your Pinecone index.", - ); -} - -(async () => { - checkRequiredEnvVars(); - await loadAndIndex(); - console.log("Finished generating storage."); -})(); diff --git a/packages/create-llama/templates/components/vectordbs/typescript/pinecone/index.ts b/packages/create-llama/templates/components/vectordbs/typescript/pinecone/index.ts deleted file mode 100644 index be18486c4..000000000 --- a/packages/create-llama/templates/components/vectordbs/typescript/pinecone/index.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* eslint-disable turbo/no-undeclared-env-vars */ -import { - ContextChatEngine, - LLM, - PineconeVectorStore, - VectorStoreIndex, - serviceContextFromDefaults, -} from "llamaindex"; -import { CHUNK_OVERLAP, CHUNK_SIZE, checkRequiredEnvVars } from "./shared.mjs"; - -async function getDataSource(llm: LLM) { - checkRequiredEnvVars(); - const serviceContext = serviceContextFromDefaults({ - llm, - chunkSize: CHUNK_SIZE, - chunkOverlap: CHUNK_OVERLAP, - }); - const store = new PineconeVectorStore(); - return await VectorStoreIndex.fromVectorStore(store, serviceContext); -} - -export async function createChatEngine(llm: LLM) { - const index = await getDataSource(llm); - const retriever = index.asRetriever({ similarityTopK: 5 }); - return new ContextChatEngine({ - chatModel: llm, - retriever, - }); -} diff --git a/packages/create-llama/templates/components/vectordbs/typescript/pinecone/shared.mjs b/packages/create-llama/templates/components/vectordbs/typescript/pinecone/shared.mjs deleted file mode 100644 index f9140261c..000000000 --- a/packages/create-llama/templates/components/vectordbs/typescript/pinecone/shared.mjs +++ /dev/null @@ -1,22 +0,0 @@ -export const STORAGE_DIR = "./data"; -export const CHUNK_SIZE = 512; -export const CHUNK_OVERLAP = 20; - -const REQUIRED_ENV_VARS = ["PINECONE_ENVIRONMENT", "PINECONE_API_KEY"]; - -export function checkRequiredEnvVars() { - const missingEnvVars = REQUIRED_ENV_VARS.filter((envVar) => { - return !process.env[envVar]; - }); - - if (missingEnvVars.length > 0) { - console.log( - `The following environment variables are required but missing: ${missingEnvVars.join( - ", ", - )}`, - ); - throw new Error( - `Missing environment variables: ${missingEnvVars.join(", ")}`, - ); - } -} diff --git a/packages/create-llama/templates/devcontainer.json b/packages/create-llama/templates/devcontainer.json deleted file mode 100644 index f87545ffb..000000000 --- a/packages/create-llama/templates/devcontainer.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "image": "mcr.microsoft.com/vscode/devcontainers/typescript-node:dev-20-bullseye", - "features": { - "ghcr.io/devcontainers-contrib/features/turborepo-npm:1": {}, - "ghcr.io/devcontainers-contrib/features/typescript:2": {}, - "ghcr.io/devcontainers/features/python:1": { - "version": "3.11", - "toolsToInstall": ["flake8", "black", "mypy", "poetry"] - } - }, - "customizations": { - "codespaces": { - "openFiles": ["README.md"] - }, - "vscode": { - "extensions": [ - "ms-vscode.typescript-language-features", - "esbenp.prettier-vscode", - "ms-python.python", - "ms-python.black-formatter", - "ms-python.vscode-flake8", - "ms-python.vscode-pylance" - ], - "settings": { - "python.formatting.provider": "black", - "python.languageServer": "Pylance", - "python.analysis.typeCheckingMode": "basic" - } - } - }, - "containerEnv": { - "POETRY_VIRTUALENVS_CREATE": "false" - }, - "forwardPorts": [3000, 8000] -} diff --git a/packages/create-llama/templates/types/simple/express/README-template.md b/packages/create-llama/templates/types/simple/express/README-template.md deleted file mode 100644 index 7ea94ab75..000000000 --- a/packages/create-llama/templates/types/simple/express/README-template.md +++ /dev/null @@ -1,50 +0,0 @@ -This is a [LlamaIndex](https://www.llamaindex.ai/) project using [Express](https://expressjs.com/) bootstrapped with [`create-llama`](https://github.com/run-llama/LlamaIndexTS/tree/main/packages/create-llama). - -## Getting Started - -First, install the dependencies: - -``` -npm install -``` - -Second, run the development server: - -``` -npm run dev -``` - -Then call the express API endpoint `/api/chat` to see the result: - -``` -curl --location 'localhost:8000/api/chat' \ ---header 'Content-Type: application/json' \ ---data '{ "messages": [{ "role": "user", "content": "Hello" }] }' -``` - -You can start editing the API by modifying `src/controllers/chat.controller.ts`. The endpoint auto-updates as you save the file. - -## Production - -First, build the project: - -``` -npm run build -``` - -You can then run the production server: - -``` -NODE_ENV=production npm run start -``` - -> Note that the `NODE_ENV` environment variable is set to `production`. This disables CORS for all origins. - -## 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! diff --git a/packages/create-llama/templates/types/simple/express/eslintrc.json b/packages/create-llama/templates/types/simple/express/eslintrc.json deleted file mode 100644 index cf20cdc7a..000000000 --- a/packages/create-llama/templates/types/simple/express/eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "eslint:recommended" -} diff --git a/packages/create-llama/templates/types/simple/express/gitignore b/packages/create-llama/templates/types/simple/express/gitignore deleted file mode 100644 index 7d5e30fc2..000000000 --- a/packages/create-llama/templates/types/simple/express/gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# local env files -.env -node_modules/ \ No newline at end of file diff --git a/packages/create-llama/templates/types/simple/express/index.ts b/packages/create-llama/templates/types/simple/express/index.ts deleted file mode 100644 index 721c4ec9d..000000000 --- a/packages/create-llama/templates/types/simple/express/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable turbo/no-undeclared-env-vars */ -import cors from "cors"; -import "dotenv/config"; -import express, { Express, Request, Response } from "express"; -import chatRouter from "./src/routes/chat.route"; - -const app: Express = express(); -const port = parseInt(process.env.PORT || "8000"); - -const env = process.env["NODE_ENV"]; -const isDevelopment = !env || env === "development"; -const prodCorsOrigin = process.env["PROD_CORS_ORIGIN"]; - -app.use(express.json()); - -if (isDevelopment) { - console.warn("Running in development mode - allowing CORS for all origins"); - app.use(cors()); -} else if (prodCorsOrigin) { - console.log( - `Running in production mode - allowing CORS for domain: ${prodCorsOrigin}`, - ); - const corsOptions = { - origin: prodCorsOrigin, // Restrict to production domain - }; - app.use(cors(corsOptions)); -} else { - console.warn("Production CORS origin not set, defaulting to no CORS."); -} - -app.use(express.text()); - -app.get("/", (req: Request, res: Response) => { - res.send("LlamaIndex Express Server"); -}); - -app.use("/api/chat", chatRouter); - -app.listen(port, () => { - console.log(`âš¡ï¸[server]: Server is running at http://localhost:${port}`); -}); diff --git a/packages/create-llama/templates/types/simple/express/package.json b/packages/create-llama/templates/types/simple/express/package.json deleted file mode 100644 index 849261e4a..000000000 --- a/packages/create-llama/templates/types/simple/express/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "llama-index-express", - "version": "1.0.0", - "main": "dist/index.js", - "type": "module", - "scripts": { - "build": "tsup index.ts --format esm --dts", - "start": "node dist/index.js", - "dev": "concurrently \"tsup index.ts --format esm --dts --watch\" \"nodemon -q dist/index.js\"" - }, - "dependencies": { - "cors": "^2.8.5", - "dotenv": "^16.3.1", - "express": "^4.18.2", - "llamaindex": "0.0.37" - }, - "devDependencies": { - "@types/cors": "^2.8.17", - "@types/express": "^4.17.21", - "@types/node": "^20.9.5", - "concurrently": "^8.2.2", - "eslint": "^8.54.0", - "nodemon": "^3.0.1", - "tsup": "^7.3.0", - "typescript": "^5.3.2" - } -} diff --git a/packages/create-llama/templates/types/simple/express/src/controllers/chat.controller.ts b/packages/create-llama/templates/types/simple/express/src/controllers/chat.controller.ts deleted file mode 100644 index 9f9639b72..000000000 --- a/packages/create-llama/templates/types/simple/express/src/controllers/chat.controller.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { Request, Response } from "express"; -import { ChatMessage, MessageContent, OpenAI } from "llamaindex"; -import { createChatEngine } from "./engine"; - -const convertMessageContent = ( - textMessage: string, - imageUrl: string | undefined, -): MessageContent => { - if (!imageUrl) return textMessage; - return [ - { - type: "text", - text: textMessage, - }, - { - type: "image_url", - image_url: { - url: imageUrl, - }, - }, - ]; -}; - -export const chat = async (req: Request, res: Response) => { - try { - const { messages, data }: { messages: ChatMessage[]; data: any } = req.body; - const userMessage = messages.pop(); - if (!messages || !userMessage || userMessage.role !== "user") { - return res.status(400).json({ - error: - "messages are required in the request body and the last message must be from the user", - }); - } - - const llm = new OpenAI({ - model: process.env.MODEL || "gpt-3.5-turbo", - }); - - // Convert message content from Vercel/AI format to LlamaIndex/OpenAI format - // Note: The non-streaming template does not need the Vercel/AI format, we're still using it for consistency with the streaming template - const userMessageContent = convertMessageContent( - userMessage.content, - data?.imageUrl, - ); - - const chatEngine = await createChatEngine(llm); - - // Calling LlamaIndex's ChatEngine to get a response - const response = await chatEngine.chat({ - message: userMessageContent, - messages, - }); - 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/types/simple/express/src/controllers/engine/index.ts b/packages/create-llama/templates/types/simple/express/src/controllers/engine/index.ts deleted file mode 100644 index abb02e90c..000000000 --- a/packages/create-llama/templates/types/simple/express/src/controllers/engine/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { LLM, SimpleChatEngine } from "llamaindex"; - -export async function createChatEngine(llm: LLM) { - return new SimpleChatEngine({ - llm, - }); -} diff --git a/packages/create-llama/templates/types/simple/express/src/routes/chat.route.ts b/packages/create-llama/templates/types/simple/express/src/routes/chat.route.ts deleted file mode 100644 index bdfeb0853..000000000 --- a/packages/create-llama/templates/types/simple/express/src/routes/chat.route.ts +++ /dev/null @@ -1,8 +0,0 @@ -import express from "express"; -import { chat } from "../controllers/chat.controller"; - -const llmRouter = express.Router(); - -llmRouter.route("/").post(chat); - -export default llmRouter; diff --git a/packages/create-llama/templates/types/simple/express/tsconfig.json b/packages/create-llama/templates/types/simple/express/tsconfig.json deleted file mode 100644 index bc819cab4..000000000 --- a/packages/create-llama/templates/types/simple/express/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "target": "es2016", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "node" - } -} diff --git a/packages/create-llama/templates/types/simple/fastapi/README-template.md b/packages/create-llama/templates/types/simple/fastapi/README-template.md deleted file mode 100644 index b1a35c42e..000000000 --- a/packages/create-llama/templates/types/simple/fastapi/README-template.md +++ /dev/null @@ -1,58 +0,0 @@ -This is a [LlamaIndex](https://www.llamaindex.ai/) project using [FastAPI](https://fastapi.tiangolo.com/) bootstrapped with [`create-llama`](https://github.com/run-llama/LlamaIndexTS/tree/main/packages/create-llama). - -## Getting Started - -First, setup the environment with poetry: - -> **_Note:_** This step is not needed if you are using the dev-container. - -``` -poetry install -poetry shell -``` - -By default, we use the OpenAI LLM (though you can customize, see app/api/routers/chat.py). As a result you need to specify an `OPENAI_API_KEY` in an .env file in this directory. - -Example `backend/.env` file: - -``` -OPENAI_API_KEY=<openai_api_key> -``` - -Second, generate the embeddings of the documents in the `./data` directory (if this folder exists - otherwise, skip this step): - -``` -python app/engine/generate.py -``` - -Third, run the development server: - -``` -python main.py -``` - -Then call the API endpoint `/api/chat` to see the result: - -``` -curl --location 'localhost:8000/api/chat' \ ---header 'Content-Type: application/json' \ ---data '{ "messages": [{ "role": "user", "content": "Hello" }] }' -``` - -You can start editing the API by modifying `app/api/routers/chat.py`. The endpoint auto-updates as you save the file. - -Open [http://localhost:8000/docs](http://localhost:8000/docs) with your browser to see the Swagger UI of the API. - -The API allows CORS for all origins to simplify development. You can change this behavior by setting the `ENVIRONMENT` environment variable to `prod`: - -``` -ENVIRONMENT=prod python main.py -``` - -## Learn More - -To learn more about LlamaIndex, take a look at the following resources: - -- [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex. - -You can check out [the LlamaIndex GitHub repository](https://github.com/run-llama/llama_index) - your feedback and contributions are welcome! diff --git a/packages/create-llama/templates/types/simple/fastapi/app/__init__.py b/packages/create-llama/templates/types/simple/fastapi/app/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/create-llama/templates/types/simple/fastapi/app/api/__init__.py b/packages/create-llama/templates/types/simple/fastapi/app/api/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/create-llama/templates/types/simple/fastapi/app/api/routers/__init__.py b/packages/create-llama/templates/types/simple/fastapi/app/api/routers/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/create-llama/templates/types/simple/fastapi/app/api/routers/chat.py b/packages/create-llama/templates/types/simple/fastapi/app/api/routers/chat.py deleted file mode 100644 index 8405f2ac5..000000000 --- a/packages/create-llama/templates/types/simple/fastapi/app/api/routers/chat.py +++ /dev/null @@ -1,54 +0,0 @@ -from typing import List -from pydantic import BaseModel -from fastapi import APIRouter, Depends, HTTPException, status -from llama_index.core.chat_engine.types import BaseChatEngine -from llama_index.core.llms import ChatMessage, MessageRole -from app.engine import get_chat_engine - -chat_router = r = APIRouter() - - -class _Message(BaseModel): - role: MessageRole - content: str - - -class _ChatData(BaseModel): - messages: List[_Message] - - -class _Result(BaseModel): - result: _Message - - -@r.post("") -async def chat( - data: _ChatData, - chat_engine: BaseChatEngine = Depends(get_chat_engine), -) -> _Result: - # check preconditions and get last message - if len(data.messages) == 0: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="No messages provided", - ) - lastMessage = data.messages.pop() - if lastMessage.role != MessageRole.USER: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Last message must be from user", - ) - # convert messages coming from the request to type ChatMessage - messages = [ - ChatMessage( - role=m.role, - content=m.content, - ) - for m in data.messages - ] - - # query chat engine - response = await chat_engine.achat(lastMessage.content, messages) - return _Result( - result=_Message(role=MessageRole.ASSISTANT, content=response.response) - ) diff --git a/packages/create-llama/templates/types/simple/fastapi/app/engine/__init__.py b/packages/create-llama/templates/types/simple/fastapi/app/engine/__init__.py deleted file mode 100644 index fd8bb96a8..000000000 --- a/packages/create-llama/templates/types/simple/fastapi/app/engine/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from llama_index.core.chat_engine import SimpleChatEngine - - -def get_chat_engine(): - return SimpleChatEngine.from_defaults() diff --git a/packages/create-llama/templates/types/simple/fastapi/app/settings.py b/packages/create-llama/templates/types/simple/fastapi/app/settings.py deleted file mode 100644 index 3f2c5e078..000000000 --- a/packages/create-llama/templates/types/simple/fastapi/app/settings.py +++ /dev/null @@ -1,41 +0,0 @@ -import os -from typing import Dict -from llama_index.core.settings import Settings -from llama_index.llms.openai import OpenAI -from llama_index.embeddings.openai import OpenAIEmbedding - - -def llm_config_from_env() -> Dict: - from llama_index.core.constants import DEFAULT_TEMPERATURE - - model = os.getenv("MODEL") - temperature = os.getenv("LLM_TEMPERATURE", DEFAULT_TEMPERATURE) - max_tokens = os.getenv("LLM_MAX_TOKENS") - - config = { - "model": model, - "temperature": float(temperature), - "max_tokens": int(max_tokens) if max_tokens is not None else None, - } - return config - - -def embedding_config_from_env() -> Dict: - model = os.getenv("EMBEDDING_MODEL") - dimension = os.getenv("EMBEDDING_DIM") - - config = { - "model": model, - "dimension": int(dimension) if dimension is not None else None, - } - return config - - -def init_settings(): - llm_configs = llm_config_from_env() - embedding_configs = embedding_config_from_env() - - Settings.llm = OpenAI(**llm_configs) - Settings.embed_model = OpenAIEmbedding(**embedding_configs) - Settings.chunk_size = int(os.getenv("CHUNK_SIZE", "1024")) - Settings.chunk_overlap = int(os.getenv("CHUNK_OVERLAP", "20")) diff --git a/packages/create-llama/templates/types/simple/fastapi/gitignore b/packages/create-llama/templates/types/simple/fastapi/gitignore deleted file mode 100644 index a6ad564cd..000000000 --- a/packages/create-llama/templates/types/simple/fastapi/gitignore +++ /dev/null @@ -1,3 +0,0 @@ -__pycache__ -storage -.env diff --git a/packages/create-llama/templates/types/simple/fastapi/main.py b/packages/create-llama/templates/types/simple/fastapi/main.py deleted file mode 100644 index 41721a8f2..000000000 --- a/packages/create-llama/templates/types/simple/fastapi/main.py +++ /dev/null @@ -1,39 +0,0 @@ -from dotenv import load_dotenv - -load_dotenv() - -import logging -import os -import uvicorn -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware -from app.api.routers.chat import chat_router -from app.settings import init_settings - -app = FastAPI() - -init_settings() - -environment = os.getenv("ENVIRONMENT", "dev") # Default to 'development' if not set - - -if environment == "dev": - logger = logging.getLogger("uvicorn") - logger.warning("Running in development mode - allowing CORS for all origins") - app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], - ) - -app.include_router(chat_router, prefix="/api/chat") - - -if __name__ == "__main__": - app_host = os.getenv("APP_HOST", "0.0.0.0") - app_port = int(os.getenv("APP_PORT", "8000")) - reload = True if environment == "dev" else False - - uvicorn.run(app="main:app", host=app_host, port=app_port, reload=reload) diff --git a/packages/create-llama/templates/types/simple/fastapi/pyproject.toml b/packages/create-llama/templates/types/simple/fastapi/pyproject.toml deleted file mode 100644 index 45c3ae567..000000000 --- a/packages/create-llama/templates/types/simple/fastapi/pyproject.toml +++ /dev/null @@ -1,17 +0,0 @@ -[tool.poetry] -name = "app" -version = "0.1.0" -description = "" -authors = ["Marcus Schiesser <mail@marcusschiesser.de>"] -readme = "README.md" - -[tool.poetry.dependencies] -python = "^3.11,<3.12" -fastapi = "^0.109.1" -uvicorn = { extras = ["standard"], version = "^0.23.2" } -python-dotenv = "^1.0.0" -llama-index = "^0.10.7" - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" diff --git a/packages/create-llama/templates/types/simple/fastapi/tests/__init__.py b/packages/create-llama/templates/types/simple/fastapi/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/create-llama/templates/types/streaming/express/README-template.md b/packages/create-llama/templates/types/streaming/express/README-template.md deleted file mode 100644 index 0e9d79610..000000000 --- a/packages/create-llama/templates/types/streaming/express/README-template.md +++ /dev/null @@ -1,50 +0,0 @@ -This is a [LlamaIndex](https://www.llamaindex.ai/) project using [Express](https://expressjs.com/) bootstrapped with [`create-llama`](https://github.com/run-llama/LlamaIndexTS/tree/main/packages/create-llama). - -## Getting Started - -First, install the dependencies: - -``` -npm install -``` - -Second, run the development server: - -``` -npm run dev -``` - -Then call the express API endpoint `/api/chat` to see the result: - -``` -curl --location 'localhost:8000/api/chat' \ ---header 'Content-Type: text/plain' \ ---data '{ "messages": [{ "role": "user", "content": "Hello" }] }' -``` - -You can start editing the API by modifying `src/controllers/chat.controller.ts`. The endpoint auto-updates as you save the file. - -## Production - -First, build the project: - -``` -npm run build -``` - -You can then run the production server: - -``` -NODE_ENV=production npm run start -``` - -> Note that the `NODE_ENV` environment variable is set to `production`. This disables CORS for all origins. - -## 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! diff --git a/packages/create-llama/templates/types/streaming/express/eslintrc.json b/packages/create-llama/templates/types/streaming/express/eslintrc.json deleted file mode 100644 index cf20cdc7a..000000000 --- a/packages/create-llama/templates/types/streaming/express/eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "eslint:recommended" -} diff --git a/packages/create-llama/templates/types/streaming/express/gitignore b/packages/create-llama/templates/types/streaming/express/gitignore deleted file mode 100644 index 7d5e30fc2..000000000 --- a/packages/create-llama/templates/types/streaming/express/gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# local env files -.env -node_modules/ \ No newline at end of file diff --git a/packages/create-llama/templates/types/streaming/express/index.ts b/packages/create-llama/templates/types/streaming/express/index.ts deleted file mode 100644 index 721c4ec9d..000000000 --- a/packages/create-llama/templates/types/streaming/express/index.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* eslint-disable turbo/no-undeclared-env-vars */ -import cors from "cors"; -import "dotenv/config"; -import express, { Express, Request, Response } from "express"; -import chatRouter from "./src/routes/chat.route"; - -const app: Express = express(); -const port = parseInt(process.env.PORT || "8000"); - -const env = process.env["NODE_ENV"]; -const isDevelopment = !env || env === "development"; -const prodCorsOrigin = process.env["PROD_CORS_ORIGIN"]; - -app.use(express.json()); - -if (isDevelopment) { - console.warn("Running in development mode - allowing CORS for all origins"); - app.use(cors()); -} else if (prodCorsOrigin) { - console.log( - `Running in production mode - allowing CORS for domain: ${prodCorsOrigin}`, - ); - const corsOptions = { - origin: prodCorsOrigin, // Restrict to production domain - }; - app.use(cors(corsOptions)); -} else { - console.warn("Production CORS origin not set, defaulting to no CORS."); -} - -app.use(express.text()); - -app.get("/", (req: Request, res: Response) => { - res.send("LlamaIndex Express Server"); -}); - -app.use("/api/chat", chatRouter); - -app.listen(port, () => { - console.log(`âš¡ï¸[server]: Server is running at http://localhost:${port}`); -}); diff --git a/packages/create-llama/templates/types/streaming/express/package.json b/packages/create-llama/templates/types/streaming/express/package.json deleted file mode 100644 index 3e46bb5fb..000000000 --- a/packages/create-llama/templates/types/streaming/express/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "llama-index-express-streaming", - "version": "1.0.0", - "main": "dist/index.js", - "type": "module", - "scripts": { - "build": "tsup index.ts --format esm --dts", - "start": "node dist/index.js", - "dev": "concurrently \"tsup index.ts --format esm --dts --watch\" \"nodemon -q dist/index.js\"" - }, - "dependencies": { - "ai": "^2.2.25", - "cors": "^2.8.5", - "dotenv": "^16.3.1", - "express": "^4.18.2", - "llamaindex": "0.0.37" - }, - "devDependencies": { - "@types/cors": "^2.8.16", - "@types/express": "^4.17.21", - "@types/node": "^20.9.5", - "concurrently": "^8.2.2", - "eslint": "^8.54.0", - "nodemon": "^3.0.1", - "tsup": "^8.0.1", - "typescript": "^5.3.2" - } -} diff --git a/packages/create-llama/templates/types/streaming/express/src/controllers/chat.controller.ts b/packages/create-llama/templates/types/streaming/express/src/controllers/chat.controller.ts deleted file mode 100644 index 9d1eb0c69..000000000 --- a/packages/create-llama/templates/types/streaming/express/src/controllers/chat.controller.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { streamToResponse } from "ai"; -import { Request, Response } from "express"; -import { ChatMessage, MessageContent, OpenAI } from "llamaindex"; -import { createChatEngine } from "./engine"; -import { LlamaIndexStream } from "./llamaindex-stream"; - -const convertMessageContent = ( - textMessage: string, - imageUrl: string | undefined, -): MessageContent => { - if (!imageUrl) return textMessage; - return [ - { - type: "text", - text: textMessage, - }, - { - type: "image_url", - image_url: { - url: imageUrl, - }, - }, - ]; -}; - -export const chat = async (req: Request, res: Response) => { - try { - const { messages, data }: { messages: ChatMessage[]; data: any } = req.body; - const userMessage = messages.pop(); - if (!messages || !userMessage || userMessage.role !== "user") { - return res.status(400).json({ - error: - "messages are required in the request body and the last message must be from the user", - }); - } - - const llm = new OpenAI({ - model: (process.env.MODEL as any) || "gpt-3.5-turbo", - }); - - const chatEngine = await createChatEngine(llm); - - // Convert message content from Vercel/AI format to LlamaIndex/OpenAI format - const userMessageContent = convertMessageContent( - userMessage.content, - data?.imageUrl, - ); - - // Calling LlamaIndex's ChatEngine to get a streamed response - const response = await chatEngine.chat({ - message: userMessageContent, - chatHistory: messages, - stream: true, - }); - - // Return a stream, which can be consumed by the Vercel/AI client - const { stream, data: streamData } = LlamaIndexStream(response, { - parserOptions: { - image_url: data?.imageUrl, - }, - }); - - // Pipe LlamaIndexStream to response - const processedStream = stream.pipeThrough(streamData.stream); - return streamToResponse(processedStream, res, { - headers: { - // response MUST have the `X-Experimental-Stream-Data: 'true'` header - // so that the client uses the correct parsing logic, see - // https://sdk.vercel.ai/docs/api-reference/stream-data#on-the-server - "X-Experimental-Stream-Data": "true", - "Content-Type": "text/plain; charset=utf-8", - "Access-Control-Expose-Headers": "X-Experimental-Stream-Data", - }, - }); - } catch (error) { - console.error("[LlamaIndex]", error); - return res.status(500).json({ - error: (error as Error).message, - }); - } -}; diff --git a/packages/create-llama/templates/types/streaming/express/src/controllers/engine/index.ts b/packages/create-llama/templates/types/streaming/express/src/controllers/engine/index.ts deleted file mode 100644 index abb02e90c..000000000 --- a/packages/create-llama/templates/types/streaming/express/src/controllers/engine/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { LLM, SimpleChatEngine } from "llamaindex"; - -export async function createChatEngine(llm: LLM) { - return new SimpleChatEngine({ - llm, - }); -} diff --git a/packages/create-llama/templates/types/streaming/express/src/controllers/llamaindex-stream.ts b/packages/create-llama/templates/types/streaming/express/src/controllers/llamaindex-stream.ts deleted file mode 100644 index 6ddd8eae6..000000000 --- a/packages/create-llama/templates/types/streaming/express/src/controllers/llamaindex-stream.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { - JSONValue, - createCallbacksTransformer, - createStreamDataTransformer, - experimental_StreamData, - trimStartOfStreamHelper, - type AIStreamCallbacksAndOptions, -} from "ai"; -import { Response } from "llamaindex"; - -type ParserOptions = { - image_url?: string; -}; - -function createParser( - res: AsyncIterable<Response>, - data: experimental_StreamData, - opts?: ParserOptions, -) { - const it = res[Symbol.asyncIterator](); - const trimStartOfStream = trimStartOfStreamHelper(); - return new ReadableStream<string>({ - start() { - // if image_url is provided, send it via the data stream - if (opts?.image_url) { - const message: JSONValue = { - type: "image_url", - image_url: { - url: opts.image_url, - }, - }; - data.append(message); - } else { - data.append({}); // send an empty image response for the user's message - } - }, - async pull(controller): Promise<void> { - const { value, done } = await it.next(); - if (done) { - controller.close(); - data.append({}); // send an empty image response for the assistant's message - data.close(); - return; - } - - const text = trimStartOfStream(value.response ?? ""); - if (text) { - controller.enqueue(text); - } - }, - }); -} - -export function LlamaIndexStream( - res: AsyncIterable<Response>, - opts?: { - callbacks?: AIStreamCallbacksAndOptions; - parserOptions?: ParserOptions; - }, -): { stream: ReadableStream; data: experimental_StreamData } { - const data = new experimental_StreamData(); - return { - stream: createParser(res, data, opts?.parserOptions) - .pipeThrough(createCallbacksTransformer(opts?.callbacks)) - .pipeThrough(createStreamDataTransformer(true)), - data, - }; -} diff --git a/packages/create-llama/templates/types/streaming/express/src/routes/chat.route.ts b/packages/create-llama/templates/types/streaming/express/src/routes/chat.route.ts deleted file mode 100644 index bdfeb0853..000000000 --- a/packages/create-llama/templates/types/streaming/express/src/routes/chat.route.ts +++ /dev/null @@ -1,8 +0,0 @@ -import express from "express"; -import { chat } from "../controllers/chat.controller"; - -const llmRouter = express.Router(); - -llmRouter.route("/").post(chat); - -export default llmRouter; diff --git a/packages/create-llama/templates/types/streaming/express/tsconfig.json b/packages/create-llama/templates/types/streaming/express/tsconfig.json deleted file mode 100644 index bc819cab4..000000000 --- a/packages/create-llama/templates/types/streaming/express/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "target": "es2016", - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "strict": true, - "skipLibCheck": true, - "moduleResolution": "node" - } -} diff --git a/packages/create-llama/templates/types/streaming/fastapi/README-template.md b/packages/create-llama/templates/types/streaming/fastapi/README-template.md deleted file mode 100644 index 35ef1125a..000000000 --- a/packages/create-llama/templates/types/streaming/fastapi/README-template.md +++ /dev/null @@ -1,58 +0,0 @@ -This is a [LlamaIndex](https://www.llamaindex.ai/) project using [FastAPI](https://fastapi.tiangolo.com/) bootstrapped with [`create-llama`](https://github.com/run-llama/LlamaIndexTS/tree/main/packages/create-llama). - -## Getting Started - -First, setup the environment with poetry: - -> **_Note:_** This step is not needed if you are using the dev-container. - -``` -poetry install -poetry shell -``` - -By default, we use the OpenAI LLM (though you can customize, see `app/settings.py`). As a result you need to specify an `OPENAI_API_KEY` in an .env file in this directory. - -Example `.env` file: - -``` -OPENAI_API_KEY=<openai_api_key> -``` - -Second, generate the embeddings of the documents in the `./data` directory (if this folder exists - otherwise, skip this step): - -``` -python app/engine/generate.py -``` - -Third, run the development server: - -``` -python main.py -``` - -Then call the API endpoint `/api/chat` to see the result: - -``` -curl --location 'localhost:8000/api/chat' \ ---header 'Content-Type: application/json' \ ---data '{ "messages": [{ "role": "user", "content": "Hello" }] }' -``` - -You can start editing the API by modifying `app/api/routers/chat.py`. The endpoint auto-updates as you save the file. - -Open [http://localhost:8000/docs](http://localhost:8000/docs) with your browser to see the Swagger UI of the API. - -The API allows CORS for all origins to simplify development. You can change this behavior by setting the `ENVIRONMENT` environment variable to `prod`: - -``` -ENVIRONMENT=prod python main.py -``` - -## Learn More - -To learn more about LlamaIndex, take a look at the following resources: - -- [LlamaIndex Documentation](https://docs.llamaindex.ai) - learn about LlamaIndex. - -You can check out [the LlamaIndex GitHub repository](https://github.com/run-llama/llama_index) - your feedback and contributions are welcome! diff --git a/packages/create-llama/templates/types/streaming/fastapi/app/__init__.py b/packages/create-llama/templates/types/streaming/fastapi/app/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/create-llama/templates/types/streaming/fastapi/app/api/__init__.py b/packages/create-llama/templates/types/streaming/fastapi/app/api/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/create-llama/templates/types/streaming/fastapi/app/api/routers/__init__.py b/packages/create-llama/templates/types/streaming/fastapi/app/api/routers/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/create-llama/templates/types/streaming/fastapi/app/api/routers/chat.py b/packages/create-llama/templates/types/streaming/fastapi/app/api/routers/chat.py deleted file mode 100644 index 278a9a753..000000000 --- a/packages/create-llama/templates/types/streaming/fastapi/app/api/routers/chat.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import List -from pydantic import BaseModel -from fastapi.responses import StreamingResponse -from fastapi import APIRouter, Depends, HTTPException, Request, status -from llama_index.core.chat_engine.types import BaseChatEngine -from llama_index.core.llms import ChatMessage, MessageRole -from app.engine import get_chat_engine - -chat_router = r = APIRouter() - - -class _Message(BaseModel): - role: MessageRole - content: str - - -class _ChatData(BaseModel): - messages: List[_Message] - - -@r.post("") -async def chat( - request: Request, - data: _ChatData, - chat_engine: BaseChatEngine = Depends(get_chat_engine), -): - # check preconditions and get last message - if len(data.messages) == 0: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="No messages provided", - ) - lastMessage = data.messages.pop() - if lastMessage.role != MessageRole.USER: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Last message must be from user", - ) - # convert messages coming from the request to type ChatMessage - messages = [ - ChatMessage( - role=m.role, - content=m.content, - ) - for m in data.messages - ] - - # query chat engine - response = await chat_engine.astream_chat(lastMessage.content, messages) - - # stream response - async def event_generator(): - async for token in response.async_response_gen(): - # If client closes connection, stop sending events - if await request.is_disconnected(): - break - yield token - - return StreamingResponse(event_generator(), media_type="text/plain") diff --git a/packages/create-llama/templates/types/streaming/fastapi/app/engine/__init__.py b/packages/create-llama/templates/types/streaming/fastapi/app/engine/__init__.py deleted file mode 100644 index fd8bb96a8..000000000 --- a/packages/create-llama/templates/types/streaming/fastapi/app/engine/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from llama_index.core.chat_engine import SimpleChatEngine - - -def get_chat_engine(): - return SimpleChatEngine.from_defaults() diff --git a/packages/create-llama/templates/types/streaming/fastapi/app/settings.py b/packages/create-llama/templates/types/streaming/fastapi/app/settings.py deleted file mode 100644 index 3f2c5e078..000000000 --- a/packages/create-llama/templates/types/streaming/fastapi/app/settings.py +++ /dev/null @@ -1,41 +0,0 @@ -import os -from typing import Dict -from llama_index.core.settings import Settings -from llama_index.llms.openai import OpenAI -from llama_index.embeddings.openai import OpenAIEmbedding - - -def llm_config_from_env() -> Dict: - from llama_index.core.constants import DEFAULT_TEMPERATURE - - model = os.getenv("MODEL") - temperature = os.getenv("LLM_TEMPERATURE", DEFAULT_TEMPERATURE) - max_tokens = os.getenv("LLM_MAX_TOKENS") - - config = { - "model": model, - "temperature": float(temperature), - "max_tokens": int(max_tokens) if max_tokens is not None else None, - } - return config - - -def embedding_config_from_env() -> Dict: - model = os.getenv("EMBEDDING_MODEL") - dimension = os.getenv("EMBEDDING_DIM") - - config = { - "model": model, - "dimension": int(dimension) if dimension is not None else None, - } - return config - - -def init_settings(): - llm_configs = llm_config_from_env() - embedding_configs = embedding_config_from_env() - - Settings.llm = OpenAI(**llm_configs) - Settings.embed_model = OpenAIEmbedding(**embedding_configs) - Settings.chunk_size = int(os.getenv("CHUNK_SIZE", "1024")) - Settings.chunk_overlap = int(os.getenv("CHUNK_OVERLAP", "20")) diff --git a/packages/create-llama/templates/types/streaming/fastapi/gitignore b/packages/create-llama/templates/types/streaming/fastapi/gitignore deleted file mode 100644 index a6ad564cd..000000000 --- a/packages/create-llama/templates/types/streaming/fastapi/gitignore +++ /dev/null @@ -1,3 +0,0 @@ -__pycache__ -storage -.env diff --git a/packages/create-llama/templates/types/streaming/fastapi/main.py b/packages/create-llama/templates/types/streaming/fastapi/main.py deleted file mode 100644 index edba3d3a7..000000000 --- a/packages/create-llama/templates/types/streaming/fastapi/main.py +++ /dev/null @@ -1,40 +0,0 @@ -from dotenv import load_dotenv - -load_dotenv() - -import logging -import os -import uvicorn -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware -from app.api.routers.chat import chat_router -from app.settings import init_settings - - -app = FastAPI() - -init_settings() - -environment = os.getenv("ENVIRONMENT", "dev") # Default to 'development' if not set - - -if environment == "dev": - logger = logging.getLogger("uvicorn") - logger.warning("Running in development mode - allowing CORS for all origins") - app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], - ) - -app.include_router(chat_router, prefix="/api/chat") - - -if __name__ == "__main__": - app_host = os.getenv("APP_HOST", "0.0.0.0") - app_port = int(os.getenv("APP_PORT", "8000")) - reload = True if environment == "dev" else False - - uvicorn.run(app="main:app", host=app_host, port=app_port, reload=reload) diff --git a/packages/create-llama/templates/types/streaming/fastapi/pyproject.toml b/packages/create-llama/templates/types/streaming/fastapi/pyproject.toml deleted file mode 100644 index 45c3ae567..000000000 --- a/packages/create-llama/templates/types/streaming/fastapi/pyproject.toml +++ /dev/null @@ -1,17 +0,0 @@ -[tool.poetry] -name = "app" -version = "0.1.0" -description = "" -authors = ["Marcus Schiesser <mail@marcusschiesser.de>"] -readme = "README.md" - -[tool.poetry.dependencies] -python = "^3.11,<3.12" -fastapi = "^0.109.1" -uvicorn = { extras = ["standard"], version = "^0.23.2" } -python-dotenv = "^1.0.0" -llama-index = "^0.10.7" - -[build-system] -requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" diff --git a/packages/create-llama/templates/types/streaming/fastapi/tests/__init__.py b/packages/create-llama/templates/types/streaming/fastapi/tests/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/packages/create-llama/templates/types/streaming/nextjs/.env.example b/packages/create-llama/templates/types/streaming/nextjs/.env.example deleted file mode 100644 index 7ac0a0155..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/.env.example +++ /dev/null @@ -1,3 +0,0 @@ -# Rename this file to `.env.local` to use environment variables locally with `next dev` -# https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables -MY_HOST="example.com" diff --git a/packages/create-llama/templates/types/streaming/nextjs/README-template.md b/packages/create-llama/templates/types/streaming/nextjs/README-template.md deleted file mode 100644 index 1509ded7c..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/README-template.md +++ /dev/null @@ -1,30 +0,0 @@ -This is a [LlamaIndex](https://www.llamaindex.ai/) project using [Next.js](https://nextjs.org/) bootstrapped with [`create-llama`](https://github.com/run-llama/LlamaIndexTS/tree/main/packages/create-llama). - -## Getting Started - -First, install the dependencies: - -``` -npm install -``` - -Second, run the development server: - -``` -npm run dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. - -## 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! diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/api/chat/engine/index.ts b/packages/create-llama/templates/types/streaming/nextjs/app/api/chat/engine/index.ts deleted file mode 100644 index abb02e90c..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/api/chat/engine/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { LLM, SimpleChatEngine } from "llamaindex"; - -export async function createChatEngine(llm: LLM) { - return new SimpleChatEngine({ - llm, - }); -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/api/chat/llamaindex-stream.ts b/packages/create-llama/templates/types/streaming/nextjs/app/api/chat/llamaindex-stream.ts deleted file mode 100644 index 6ddd8eae6..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/api/chat/llamaindex-stream.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { - JSONValue, - createCallbacksTransformer, - createStreamDataTransformer, - experimental_StreamData, - trimStartOfStreamHelper, - type AIStreamCallbacksAndOptions, -} from "ai"; -import { Response } from "llamaindex"; - -type ParserOptions = { - image_url?: string; -}; - -function createParser( - res: AsyncIterable<Response>, - data: experimental_StreamData, - opts?: ParserOptions, -) { - const it = res[Symbol.asyncIterator](); - const trimStartOfStream = trimStartOfStreamHelper(); - return new ReadableStream<string>({ - start() { - // if image_url is provided, send it via the data stream - if (opts?.image_url) { - const message: JSONValue = { - type: "image_url", - image_url: { - url: opts.image_url, - }, - }; - data.append(message); - } else { - data.append({}); // send an empty image response for the user's message - } - }, - async pull(controller): Promise<void> { - const { value, done } = await it.next(); - if (done) { - controller.close(); - data.append({}); // send an empty image response for the assistant's message - data.close(); - return; - } - - const text = trimStartOfStream(value.response ?? ""); - if (text) { - controller.enqueue(text); - } - }, - }); -} - -export function LlamaIndexStream( - res: AsyncIterable<Response>, - opts?: { - callbacks?: AIStreamCallbacksAndOptions; - parserOptions?: ParserOptions; - }, -): { stream: ReadableStream; data: experimental_StreamData } { - const data = new experimental_StreamData(); - return { - stream: createParser(res, data, opts?.parserOptions) - .pipeThrough(createCallbacksTransformer(opts?.callbacks)) - .pipeThrough(createStreamDataTransformer(true)), - data, - }; -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/api/chat/route.ts b/packages/create-llama/templates/types/streaming/nextjs/app/api/chat/route.ts deleted file mode 100644 index ef35bf76e..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/api/chat/route.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { StreamingTextResponse } from "ai"; -import { ChatMessage, MessageContent, OpenAI } from "llamaindex"; -import { NextRequest, NextResponse } from "next/server"; -import { createChatEngine } from "./engine"; -import { LlamaIndexStream } from "./llamaindex-stream"; - -export const runtime = "nodejs"; -export const dynamic = "force-dynamic"; - -const convertMessageContent = ( - textMessage: string, - imageUrl: string | undefined, -): MessageContent => { - if (!imageUrl) return textMessage; - return [ - { - type: "text", - text: textMessage, - }, - { - type: "image_url", - image_url: { - url: imageUrl, - }, - }, - ]; -}; - -export async function POST(request: NextRequest) { - try { - const body = await request.json(); - const { messages, data }: { messages: ChatMessage[]; data: any } = body; - const userMessage = messages.pop(); - if (!messages || !userMessage || userMessage.role !== "user") { - return NextResponse.json( - { - error: - "messages are required in the request body and the last message must be from the user", - }, - { status: 400 }, - ); - } - - const llm = new OpenAI({ - model: (process.env.MODEL as any) ?? "gpt-3.5-turbo", - maxTokens: 512, - }); - - const chatEngine = await createChatEngine(llm); - - // Convert message content from Vercel/AI format to LlamaIndex/OpenAI format - const userMessageContent = convertMessageContent( - userMessage.content, - data?.imageUrl, - ); - - // Calling LlamaIndex's ChatEngine to get a streamed response - const response = await chatEngine.chat({ - message: userMessageContent, - chatHistory: messages, - stream: true, - }); - - // Transform LlamaIndex stream to Vercel/AI format - const { stream, data: streamData } = LlamaIndexStream(response, { - parserOptions: { - image_url: data?.imageUrl, - }, - }); - - // Return a StreamingTextResponse, which can be consumed by the Vercel/AI client - return new StreamingTextResponse(stream, {}, streamData); - } catch (error) { - console.error("[LlamaIndex]", error); - return NextResponse.json( - { - error: (error as Error).message, - }, - { - status: 500, - }, - ); - } -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/chat-section.tsx b/packages/create-llama/templates/types/streaming/nextjs/app/components/chat-section.tsx deleted file mode 100644 index 08afc2548..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/chat-section.tsx +++ /dev/null @@ -1,46 +0,0 @@ -"use client"; - -import { useChat } from "ai/react"; -import { useMemo } from "react"; -import { insertDataIntoMessages } from "./transform"; -import { ChatInput, ChatMessages } from "./ui/chat"; - -export default function ChatSection() { - const { - messages, - input, - isLoading, - handleSubmit, - handleInputChange, - reload, - stop, - data, - } = useChat({ - api: process.env.NEXT_PUBLIC_CHAT_API, - headers: { - "Content-Type": "application/json", // using JSON because of vercel/ai 2.2.26 - }, - }); - - const transformedMessages = useMemo(() => { - return insertDataIntoMessages(messages, data); - }, [messages, data]); - - return ( - <div className="space-y-4 max-w-5xl w-full"> - <ChatMessages - messages={transformedMessages} - isLoading={isLoading} - reload={reload} - stop={stop} - /> - <ChatInput - input={input} - handleSubmit={handleSubmit} - handleInputChange={handleInputChange} - isLoading={isLoading} - multiModal={process.env.NEXT_PUBLIC_MODEL === "gpt-4-vision-preview"} - /> - </div> - ); -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/header.tsx b/packages/create-llama/templates/types/streaming/nextjs/app/components/header.tsx deleted file mode 100644 index 2b0e488f7..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/header.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import Image from "next/image"; - -export default function Header() { - return ( - <div className="z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex"> - <p className="fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30"> - Get started by editing - <code className="font-mono font-bold">app/page.tsx</code> - </p> - <div className="fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none"> - <a - href="https://www.llamaindex.ai/" - className="flex items-center justify-center font-nunito text-lg font-bold gap-2" - > - <span>Built by LlamaIndex</span> - <Image - className="rounded-xl" - src="/llama.png" - alt="Llama Logo" - width={40} - height={40} - priority - /> - </a> - </div> - </div> - ); -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/transform.ts b/packages/create-llama/templates/types/streaming/nextjs/app/components/transform.ts deleted file mode 100644 index 5af8fb3cb..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/transform.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { JSONValue, Message } from "ai"; - -export const isValidMessageData = (rawData: JSONValue | undefined) => { - if (!rawData || typeof rawData !== "object") return false; - if (Object.keys(rawData).length === 0) return false; - return true; -}; - -export const insertDataIntoMessages = ( - messages: Message[], - data: JSONValue[] | undefined, -) => { - if (!data) return messages; - messages.forEach((message, i) => { - const rawData = data[i]; - if (isValidMessageData(rawData)) message.data = rawData; - }); - return messages; -}; diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/README-template.md b/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/README-template.md deleted file mode 100644 index ebfcf48c9..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/README-template.md +++ /dev/null @@ -1 +0,0 @@ -Using the chat component from https://github.com/marcusschiesser/ui (based on https://ui.shadcn.com/) diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/button.tsx b/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/button.tsx deleted file mode 100644 index 662b0404d..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/button.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { Slot } from "@radix-ui/react-slot"; -import { cva, type VariantProps } from "class-variance-authority"; -import * as React from "react"; - -import { cn } from "./lib/utils"; - -const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", - { - variants: { - variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90", - destructive: - "bg-destructive text-destructive-foreground hover:bg-destructive/90", - outline: - "border border-input bg-background hover:bg-accent hover:text-accent-foreground", - secondary: - "bg-secondary text-secondary-foreground hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline", - }, - size: { - default: "h-10 px-4 py-2", - sm: "h-9 rounded-md px-3", - lg: "h-11 rounded-md px-8", - icon: "h-10 w-10", - }, - }, - defaultVariants: { - variant: "default", - size: "default", - }, - }, -); - -export interface ButtonProps - extends React.ButtonHTMLAttributes<HTMLButtonElement>, - VariantProps<typeof buttonVariants> { - asChild?: boolean; -} - -const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( - ({ className, variant, size, asChild = false, ...props }, ref) => { - const Comp = asChild ? Slot : "button"; - return ( - <Comp - className={cn(buttonVariants({ variant, size, className }))} - ref={ref} - {...props} - /> - ); - }, -); -Button.displayName = "Button"; - -export { Button, buttonVariants }; diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-actions.tsx b/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-actions.tsx deleted file mode 100644 index 151ef61a9..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-actions.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { PauseCircle, RefreshCw } from "lucide-react"; - -import { Button } from "../button"; -import { ChatHandler } from "./chat.interface"; - -export default function ChatActions( - props: Pick<ChatHandler, "stop" | "reload"> & { - showReload?: boolean; - showStop?: boolean; - }, -) { - return ( - <div className="space-x-4"> - {props.showStop && ( - <Button variant="outline" size="sm" onClick={props.stop}> - <PauseCircle className="mr-2 h-4 w-4" /> - Stop generating - </Button> - )} - {props.showReload && ( - <Button variant="outline" size="sm" onClick={props.reload}> - <RefreshCw className="mr-2 h-4 w-4" /> - Regenerate - </Button> - )} - </div> - ); -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-avatar.tsx b/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-avatar.tsx deleted file mode 100644 index ce04e306a..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-avatar.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { User2 } from "lucide-react"; -import Image from "next/image"; - -export default function ChatAvatar({ role }: { role: string }) { - if (role === "user") { - return ( - <div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border bg-background shadow"> - <User2 className="h-4 w-4" /> - </div> - ); - } - - return ( - <div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border bg-black text-white shadow"> - <Image - className="rounded-md" - src="/llama.png" - alt="Llama Logo" - width={24} - height={24} - priority - /> - </div> - ); -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-input.tsx b/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-input.tsx deleted file mode 100644 index 435637e5e..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-input.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { useState } from "react"; -import { Button } from "../button"; -import FileUploader from "../file-uploader"; -import { Input } from "../input"; -import UploadImagePreview from "../upload-image-preview"; -import { ChatHandler } from "./chat.interface"; - -export default function ChatInput( - props: Pick< - ChatHandler, - | "isLoading" - | "input" - | "onFileUpload" - | "onFileError" - | "handleSubmit" - | "handleInputChange" - > & { - multiModal?: boolean; - }, -) { - const [imageUrl, setImageUrl] = useState<string | null>(null); - - const onSubmit = (e: React.FormEvent<HTMLFormElement>) => { - if (imageUrl) { - props.handleSubmit(e, { - data: { imageUrl: imageUrl }, - }); - setImageUrl(null); - return; - } - props.handleSubmit(e); - }; - - const onRemovePreviewImage = () => setImageUrl(null); - - const handleUploadImageFile = async (file: File) => { - const base64 = await new Promise<string>((resolve, reject) => { - const reader = new FileReader(); - reader.readAsDataURL(file); - reader.onload = () => resolve(reader.result as string); - reader.onerror = (error) => reject(error); - }); - setImageUrl(base64); - }; - - const handleUploadFile = async (file: File) => { - try { - if (props.multiModal && file.type.startsWith("image/")) { - return await handleUploadImageFile(file); - } - props.onFileUpload?.(file); - } catch (error: any) { - props.onFileError?.(error.message); - } - }; - - return ( - <form - onSubmit={onSubmit} - className="rounded-xl bg-white p-4 shadow-xl space-y-4" - > - {imageUrl && ( - <UploadImagePreview url={imageUrl} onRemove={onRemovePreviewImage} /> - )} - <div className="flex w-full items-start justify-between gap-4 "> - <Input - autoFocus - name="message" - placeholder="Type a message" - className="flex-1" - value={props.input} - onChange={props.handleInputChange} - /> - <FileUploader - onFileUpload={handleUploadFile} - onFileError={props.onFileError} - /> - <Button type="submit" disabled={props.isLoading}> - Send message - </Button> - </div> - </form> - ); -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-message.tsx b/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-message.tsx deleted file mode 100644 index 808d9b080..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-message.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { Check, Copy } from "lucide-react"; - -import { JSONValue, Message } from "ai"; -import Image from "next/image"; -import { Button } from "../button"; -import ChatAvatar from "./chat-avatar"; -import Markdown from "./markdown"; -import { useCopyToClipboard } from "./use-copy-to-clipboard"; - -interface ChatMessageImageData { - type: "image_url"; - image_url: { - url: string; - }; -} - -// This component will parse message data and render the appropriate UI. -function ChatMessageData({ messageData }: { messageData: JSONValue }) { - const { image_url, type } = messageData as unknown as ChatMessageImageData; - if (type === "image_url") { - return ( - <div className="rounded-md max-w-[200px] shadow-md"> - <Image - src={image_url.url} - width={0} - height={0} - sizes="100vw" - style={{ width: "100%", height: "auto" }} - alt="" - /> - </div> - ); - } - return null; -} - -export default function ChatMessage(chatMessage: Message) { - const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 }); - return ( - <div className="flex items-start gap-4 pr-5 pt-5"> - <ChatAvatar role={chatMessage.role} /> - <div className="group flex flex-1 justify-between gap-2"> - <div className="flex-1 space-y-4"> - {chatMessage.data && ( - <ChatMessageData messageData={chatMessage.data} /> - )} - <Markdown content={chatMessage.content} /> - </div> - <Button - onClick={() => copyToClipboard(chatMessage.content)} - size="icon" - variant="ghost" - className="h-8 w-8 opacity-0 group-hover:opacity-100" - > - {isCopied ? ( - <Check className="h-4 w-4" /> - ) : ( - <Copy className="h-4 w-4" /> - )} - </Button> - </div> - </div> - ); -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-messages.tsx b/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-messages.tsx deleted file mode 100644 index abc3e52d7..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat-messages.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { Loader2 } from "lucide-react"; -import { useEffect, useRef } from "react"; - -import ChatActions from "./chat-actions"; -import ChatMessage from "./chat-message"; -import { ChatHandler } from "./chat.interface"; - -export default function ChatMessages( - props: Pick<ChatHandler, "messages" | "isLoading" | "reload" | "stop">, -) { - const scrollableChatContainerRef = useRef<HTMLDivElement>(null); - const messageLength = props.messages.length; - const lastMessage = props.messages[messageLength - 1]; - - const scrollToBottom = () => { - if (scrollableChatContainerRef.current) { - scrollableChatContainerRef.current.scrollTop = - scrollableChatContainerRef.current.scrollHeight; - } - }; - - const isLastMessageFromAssistant = - messageLength > 0 && lastMessage?.role !== "user"; - const showReload = - props.reload && !props.isLoading && isLastMessageFromAssistant; - const showStop = props.stop && props.isLoading; - - // `isPending` indicate - // that stream response is not yet received from the server, - // so we show a loading indicator to give a better UX. - const isPending = props.isLoading && !isLastMessageFromAssistant; - - useEffect(() => { - scrollToBottom(); - }, [messageLength, lastMessage]); - - return ( - <div className="w-full rounded-xl bg-white p-4 shadow-xl pb-0"> - <div - className="flex h-[50vh] flex-col gap-5 divide-y overflow-y-auto pb-4" - ref={scrollableChatContainerRef} - > - {props.messages.map((m) => ( - <ChatMessage key={m.id} {...m} /> - ))} - {isPending && ( - <div className="flex justify-center items-center pt-10"> - <Loader2 className="h-4 w-4 animate-spin" /> - </div> - )} - </div> - <div className="flex justify-end py-4"> - <ChatActions - reload={props.reload} - stop={props.stop} - showReload={showReload} - showStop={showStop} - /> - </div> - </div> - ); -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat.interface.ts b/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat.interface.ts deleted file mode 100644 index 5b9f22539..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/chat.interface.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Message } from "ai"; - -export interface ChatHandler { - messages: Message[]; - input: string; - isLoading: boolean; - handleSubmit: ( - e: React.FormEvent<HTMLFormElement>, - ops?: { - data?: any; - }, - ) => void; - handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void; - reload?: () => void; - stop?: () => void; - onFileUpload?: (file: File) => Promise<void>; - onFileError?: (errMsg: string) => void; -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/codeblock.tsx b/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/codeblock.tsx deleted file mode 100644 index 014a0fc3a..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/codeblock.tsx +++ /dev/null @@ -1,139 +0,0 @@ -"use client"; - -import { Check, Copy, Download } from "lucide-react"; -import { FC, memo } from "react"; -import { Prism, SyntaxHighlighterProps } from "react-syntax-highlighter"; -import { coldarkDark } from "react-syntax-highlighter/dist/cjs/styles/prism"; - -import { Button } from "../button"; -import { useCopyToClipboard } from "./use-copy-to-clipboard"; - -// TODO: Remove this when @type/react-syntax-highlighter is updated -const SyntaxHighlighter = Prism as unknown as FC<SyntaxHighlighterProps>; - -interface Props { - language: string; - value: string; -} - -interface languageMap { - [key: string]: string | undefined; -} - -export const programmingLanguages: languageMap = { - javascript: ".js", - python: ".py", - java: ".java", - c: ".c", - cpp: ".cpp", - "c++": ".cpp", - "c#": ".cs", - ruby: ".rb", - php: ".php", - swift: ".swift", - "objective-c": ".m", - kotlin: ".kt", - typescript: ".ts", - go: ".go", - perl: ".pl", - rust: ".rs", - scala: ".scala", - haskell: ".hs", - lua: ".lua", - shell: ".sh", - sql: ".sql", - html: ".html", - css: ".css", - // add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component -}; - -export const generateRandomString = (length: number, lowercase = false) => { - const chars = "ABCDEFGHJKLMNPQRSTUVWXY3456789"; // excluding similar looking characters like Z, 2, I, 1, O, 0 - let result = ""; - for (let i = 0; i < length; i++) { - result += chars.charAt(Math.floor(Math.random() * chars.length)); - } - return lowercase ? result.toLowerCase() : result; -}; - -const CodeBlock: FC<Props> = memo(({ language, value }) => { - const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 }); - - const downloadAsFile = () => { - if (typeof window === "undefined") { - return; - } - const fileExtension = programmingLanguages[language] || ".file"; - const suggestedFileName = `file-${generateRandomString( - 3, - true, - )}${fileExtension}`; - const fileName = window.prompt("Enter file name" || "", suggestedFileName); - - if (!fileName) { - // User pressed cancel on prompt. - return; - } - - const blob = new Blob([value], { type: "text/plain" }); - const url = URL.createObjectURL(blob); - const link = document.createElement("a"); - link.download = fileName; - link.href = url; - link.style.display = "none"; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(url); - }; - - const onCopy = () => { - if (isCopied) return; - copyToClipboard(value); - }; - - return ( - <div className="codeblock relative w-full bg-zinc-950 font-sans"> - <div className="flex w-full items-center justify-between bg-zinc-800 px-6 py-2 pr-4 text-zinc-100"> - <span className="text-xs lowercase">{language}</span> - <div className="flex items-center space-x-1"> - <Button variant="ghost" onClick={downloadAsFile} size="icon"> - <Download /> - <span className="sr-only">Download</span> - </Button> - <Button variant="ghost" size="icon" onClick={onCopy}> - {isCopied ? ( - <Check className="h-4 w-4" /> - ) : ( - <Copy className="h-4 w-4" /> - )} - <span className="sr-only">Copy code</span> - </Button> - </div> - </div> - <SyntaxHighlighter - language={language} - style={coldarkDark} - PreTag="div" - showLineNumbers - customStyle={{ - width: "100%", - background: "transparent", - padding: "1.5rem 1rem", - borderRadius: "0.5rem", - }} - codeTagProps={{ - style: { - fontSize: "0.9rem", - fontFamily: "var(--font-mono)", - }, - }} - > - {value} - </SyntaxHighlighter> - </div> - ); -}); -CodeBlock.displayName = "CodeBlock"; - -export { CodeBlock }; diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/index.ts b/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/index.ts deleted file mode 100644 index 112ef39a8..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import ChatInput from "./chat-input"; -import ChatMessages from "./chat-messages"; - -export { type ChatHandler } from "./chat.interface"; -export { ChatInput, ChatMessages }; diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/markdown.tsx b/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/markdown.tsx deleted file mode 100644 index 3ca538051..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/markdown.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { FC, memo } from "react"; -import ReactMarkdown, { Options } from "react-markdown"; -import remarkGfm from "remark-gfm"; -import remarkMath from "remark-math"; - -import { CodeBlock } from "./codeblock"; - -const MemoizedReactMarkdown: FC<Options> = memo( - ReactMarkdown, - (prevProps, nextProps) => - prevProps.children === nextProps.children && - prevProps.className === nextProps.className, -); - -export default function Markdown({ content }: { content: string }) { - return ( - <MemoizedReactMarkdown - className="prose dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 break-words" - remarkPlugins={[remarkGfm, remarkMath]} - components={{ - p({ children }) { - return <p className="mb-2 last:mb-0">{children}</p>; - }, - code({ node, inline, className, children, ...props }) { - if (children.length) { - if (children[0] == "â–") { - return ( - <span className="mt-1 animate-pulse cursor-default">â–</span> - ); - } - - children[0] = (children[0] as string).replace("`â–`", "â–"); - } - - const match = /language-(\w+)/.exec(className || ""); - - if (inline) { - return ( - <code className={className} {...props}> - {children} - </code> - ); - } - - return ( - <CodeBlock - key={Math.random()} - language={(match && match[1]) || ""} - value={String(children).replace(/\n$/, "")} - {...props} - /> - ); - }, - }} - > - {content} - </MemoizedReactMarkdown> - ); -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/use-copy-to-clipboard.tsx b/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/use-copy-to-clipboard.tsx deleted file mode 100644 index e011d69bd..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/chat/use-copy-to-clipboard.tsx +++ /dev/null @@ -1,33 +0,0 @@ -"use client"; - -import * as React from "react"; - -export interface useCopyToClipboardProps { - timeout?: number; -} - -export function useCopyToClipboard({ - timeout = 2000, -}: useCopyToClipboardProps) { - const [isCopied, setIsCopied] = React.useState<Boolean>(false); - - const copyToClipboard = (value: string) => { - if (typeof window === "undefined" || !navigator.clipboard?.writeText) { - return; - } - - if (!value) { - return; - } - - navigator.clipboard.writeText(value).then(() => { - setIsCopied(true); - - setTimeout(() => { - setIsCopied(false); - }, timeout); - }); - }; - - return { isCopied, copyToClipboard }; -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/file-uploader.tsx b/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/file-uploader.tsx deleted file mode 100644 index e42a267d1..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/file-uploader.tsx +++ /dev/null @@ -1,105 +0,0 @@ -"use client"; - -import { Loader2, Paperclip } from "lucide-react"; -import { ChangeEvent, useState } from "react"; -import { buttonVariants } from "./button"; -import { cn } from "./lib/utils"; - -export interface FileUploaderProps { - config?: { - inputId?: string; - fileSizeLimit?: number; - allowedExtensions?: string[]; - checkExtension?: (extension: string) => string | null; - disabled: boolean; - }; - onFileUpload: (file: File) => Promise<void>; - onFileError?: (errMsg: string) => void; -} - -const DEFAULT_INPUT_ID = "fileInput"; -const DEFAULT_FILE_SIZE_LIMIT = 1024 * 1024 * 50; // 50 MB - -export default function FileUploader({ - config, - onFileUpload, - onFileError, -}: FileUploaderProps) { - const [uploading, setUploading] = useState(false); - - const inputId = config?.inputId || DEFAULT_INPUT_ID; - const fileSizeLimit = config?.fileSizeLimit || DEFAULT_FILE_SIZE_LIMIT; - const allowedExtensions = config?.allowedExtensions; - const defaultCheckExtension = (extension: string) => { - if (allowedExtensions && !allowedExtensions.includes(extension)) { - return `Invalid file type. Please select a file with one of these formats: ${allowedExtensions!.join( - ",", - )}`; - } - return null; - }; - const checkExtension = config?.checkExtension ?? defaultCheckExtension; - - const isFileSizeExceeded = (file: File) => { - return file.size > fileSizeLimit; - }; - - const resetInput = () => { - const fileInput = document.getElementById(inputId) as HTMLInputElement; - fileInput.value = ""; - }; - - const onFileChange = async (e: ChangeEvent<HTMLInputElement>) => { - const file = e.target.files?.[0]; - if (!file) return; - - setUploading(true); - await handleUpload(file); - resetInput(); - setUploading(false); - }; - - const handleUpload = async (file: File) => { - const onFileUploadError = onFileError || window.alert; - const fileExtension = file.name.split(".").pop() || ""; - const extensionFileError = checkExtension(fileExtension); - if (extensionFileError) { - return onFileUploadError(extensionFileError); - } - - if (isFileSizeExceeded(file)) { - return onFileUploadError( - `File size exceeded. Limit is ${fileSizeLimit / 1024 / 1024} MB`, - ); - } - - await onFileUpload(file); - }; - - return ( - <div className="self-stretch"> - <input - type="file" - id={inputId} - style={{ display: "none" }} - onChange={onFileChange} - accept={allowedExtensions?.join(",")} - disabled={config?.disabled || uploading} - /> - <label - htmlFor={inputId} - className={cn( - buttonVariants({ variant: "secondary", size: "icon" }), - "cursor-pointer", - uploading && "opacity-50", - )} - > - {uploading ? ( - <Loader2 className="h-4 w-4 animate-spin" /> - ) : ( - <Paperclip className="-rotate-45 w-4 h-4" /> - )} - </label> - </div> - ); -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/input.tsx b/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/input.tsx deleted file mode 100644 index edfa129e6..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/input.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import * as React from "react"; - -import { cn } from "./lib/utils"; - -export interface InputProps - extends React.InputHTMLAttributes<HTMLInputElement> {} - -const Input = React.forwardRef<HTMLInputElement, InputProps>( - ({ className, type, ...props }, ref) => { - return ( - <input - type={type} - className={cn( - "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", - className, - )} - ref={ref} - {...props} - /> - ); - }, -); -Input.displayName = "Input"; - -export { Input }; diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/lib/utils.ts b/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/lib/utils.ts deleted file mode 100644 index a5ef19350..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/lib/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { clsx, type ClassValue } from "clsx"; -import { twMerge } from "tailwind-merge"; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/upload-image-preview.tsx b/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/upload-image-preview.tsx deleted file mode 100644 index 55ef6e9c2..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/components/ui/upload-image-preview.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { XCircleIcon } from "lucide-react"; -import Image from "next/image"; -import { cn } from "./lib/utils"; - -export default function UploadImagePreview({ - url, - onRemove, -}: { - url: string; - onRemove: () => void; -}) { - return ( - <div className="relative w-20 h-20 group"> - <Image - src={url} - alt="Uploaded image" - fill - className="object-cover w-full h-full rounded-xl hover:brightness-75" - /> - <div - className={cn( - "absolute -top-2 -right-2 w-6 h-6 z-10 bg-gray-500 text-white rounded-full hidden group-hover:block", - )} - > - <XCircleIcon - className="w-6 h-6 bg-gray-500 text-white rounded-full" - onClick={onRemove} - /> - </div> - </div> - ); -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/favicon.ico b/packages/create-llama/templates/types/streaming/nextjs/app/favicon.ico deleted file mode 100644 index a1eaef62f2dfa895f1bbffc6595bb53d9604963e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15406 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 KfA0a-1OEqKLE94m diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/globals.css b/packages/create-llama/templates/types/streaming/nextjs/app/globals.css deleted file mode 100644 index 09b85ed2c..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/globals.css +++ /dev/null @@ -1,94 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -@layer base { - :root { - --background: 0 0% 100%; - --foreground: 222.2 47.4% 11.2%; - - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; - - --popover: 0 0% 100%; - --popover-foreground: 222.2 47.4% 11.2%; - - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - - --card: 0 0% 100%; - --card-foreground: 222.2 47.4% 11.2%; - - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; - - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; - - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; - - --destructive: 0 100% 50%; - --destructive-foreground: 210 40% 98%; - - --ring: 215 20.2% 65.1%; - - --radius: 0.5rem; - } - - .dark { - --background: 224 71% 4%; - --foreground: 213 31% 91%; - - --muted: 223 47% 11%; - --muted-foreground: 215.4 16.3% 56.9%; - - --accent: 216 34% 17%; - --accent-foreground: 210 40% 98%; - - --popover: 224 71% 4%; - --popover-foreground: 215 20.2% 65.1%; - - --border: 216 34% 17%; - --input: 216 34% 17%; - - --card: 224 71% 4%; - --card-foreground: 213 31% 91%; - - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 1.2%; - - --secondary: 222.2 47.4% 11.2%; - --secondary-foreground: 210 40% 98%; - - --destructive: 0 63% 31%; - --destructive-foreground: 210 40% 98%; - - --ring: 216 34% 17%; - - --radius: 0.5rem; - } -} - -@layer base { - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - font-feature-settings: - "rlig" 1, - "calt" 1; - } - .background-gradient { - background-color: #fff; - background-image: radial-gradient( - at 21% 11%, - rgba(186, 186, 233, 0.53) 0, - transparent 50% - ), - radial-gradient(at 85% 0, hsla(46, 57%, 78%, 0.52) 0, transparent 50%), - radial-gradient(at 91% 36%, rgba(194, 213, 255, 0.68) 0, transparent 50%), - radial-gradient(at 8% 40%, rgba(251, 218, 239, 0.46) 0, transparent 50%); - } -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/layout.tsx b/packages/create-llama/templates/types/streaming/nextjs/app/layout.tsx deleted file mode 100644 index fb0977062..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/layout.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { Metadata } from "next"; -import { Inter } from "next/font/google"; -import "./globals.css"; - -const inter = Inter({ subsets: ["latin"] }); - -export const metadata: Metadata = { - title: "Create Llama App", - description: "Generated by create-llama", -}; - -export default function RootLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( - <html lang="en"> - <body className={inter.className}>{children}</body> - </html> - ); -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/app/page.tsx b/packages/create-llama/templates/types/streaming/nextjs/app/page.tsx deleted file mode 100644 index ef00262b4..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/app/page.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import Header from "@/app/components/header"; -import ChatSection from "./components/chat-section"; - -export default function Home() { - return ( - <main className="flex min-h-screen flex-col items-center gap-10 p-24 background-gradient"> - <Header /> - <ChatSection /> - </main> - ); -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/eslintrc.json b/packages/create-llama/templates/types/streaming/nextjs/eslintrc.json deleted file mode 100644 index bffb357a7..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "next/core-web-vitals" -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/gitignore b/packages/create-llama/templates/types/streaming/nextjs/gitignore deleted file mode 100644 index 8f322f0d8..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/gitignore +++ /dev/null @@ -1,35 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env*.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts diff --git a/packages/create-llama/templates/types/streaming/nextjs/next-env.d.ts b/packages/create-llama/templates/types/streaming/nextjs/next-env.d.ts deleted file mode 100644 index 4f11a03dc..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/next-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// <reference types="next" /> -/// <reference types="next/image-types/global" /> - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/packages/create-llama/templates/types/streaming/nextjs/next.config.json b/packages/create-llama/templates/types/streaming/nextjs/next.config.json deleted file mode 100644 index 264e20ef3..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/next.config.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "experimental": { - "outputFileTracingIncludes": { - "/*": ["./cache/**/*"] - } - } -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/next.config.mjs b/packages/create-llama/templates/types/streaming/nextjs/next.config.mjs deleted file mode 100644 index 124122bfa..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/next.config.mjs +++ /dev/null @@ -1,8 +0,0 @@ -/** @type {import('next').NextConfig} */ -import fs from "fs"; -import webpack from "./webpack.config.mjs"; - -const nextConfig = JSON.parse(fs.readFileSync("./next.config.json", "utf-8")); -nextConfig.webpack = webpack; - -export default nextConfig; diff --git a/packages/create-llama/templates/types/streaming/nextjs/package.json b/packages/create-llama/templates/types/streaming/nextjs/package.json deleted file mode 100644 index 2f23029dd..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/package.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "llama-index-nextjs-streaming", - "version": "1.0.0", - "scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start", - "lint": "next lint" - }, - "dependencies": { - "@radix-ui/react-slot": "^1.0.2", - "ai": "^2.2.27", - "class-variance-authority": "^0.7.0", - "clsx": "^1.2.1", - "dotenv": "^16.3.1", - "llamaindex": "0.0.37", - "lucide-react": "^0.294.0", - "next": "^14.0.3", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-markdown": "^8.0.7", - "react-syntax-highlighter": "^15.5.0", - "remark": "^14.0.3", - "remark-code-import": "^1.2.0", - "remark-gfm": "^3.0.1", - "remark-math": "^5.1.1", - "supports-color": "^9.4.0", - "tailwind-merge": "^2.1.0" - }, - "devDependencies": { - "@types/node": "^20.10.3", - "@types/react": "^18.2.42", - "@types/react-dom": "^18.2.17", - "autoprefixer": "^10.4.16", - "eslint": "^8.55.0", - "eslint-config-next": "^14.0.3", - "postcss": "^8.4.32", - "tailwindcss": "^3.3.6", - "typescript": "^5.3.2", - "@types/react-syntax-highlighter": "^15.5.11", - "cross-env": "^7.0.3" - } -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/postcss.config.js b/packages/create-llama/templates/types/streaming/nextjs/postcss.config.js deleted file mode 100644 index 12a703d90..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/packages/create-llama/templates/types/streaming/nextjs/public/llama.png b/packages/create-llama/templates/types/streaming/nextjs/public/llama.png deleted file mode 100644 index d4efba3b816bf765439c6d01b322b02684e946c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36985 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 diff --git a/packages/create-llama/templates/types/streaming/nextjs/tailwind.config.ts b/packages/create-llama/templates/types/streaming/nextjs/tailwind.config.ts deleted file mode 100644 index aa5580aff..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/tailwind.config.ts +++ /dev/null @@ -1,78 +0,0 @@ -import type { Config } from "tailwindcss"; -import { fontFamily } from "tailwindcss/defaultTheme"; - -const config: Config = { - darkMode: ["class"], - content: ["app/**/*.{ts,tsx}", "components/**/*.{ts,tsx}"], - theme: { - container: { - center: true, - padding: "2rem", - screens: { - "2xl": "1400px", - }, - }, - extend: { - colors: { - border: "hsl(var(--border))", - input: "hsl(var(--input))", - ring: "hsl(var(--ring))", - background: "hsl(var(--background))", - foreground: "hsl(var(--foreground))", - primary: { - DEFAULT: "hsl(var(--primary))", - foreground: "hsl(var(--primary-foreground))", - }, - secondary: { - DEFAULT: "hsl(var(--secondary))", - foreground: "hsl(var(--secondary-foreground))", - }, - destructive: { - DEFAULT: "hsl(var(--destructive) / <alpha-value>)", - foreground: "hsl(var(--destructive-foreground) / <alpha-value>)", - }, - muted: { - DEFAULT: "hsl(var(--muted))", - foreground: "hsl(var(--muted-foreground))", - }, - accent: { - DEFAULT: "hsl(var(--accent))", - foreground: "hsl(var(--accent-foreground))", - }, - popover: { - DEFAULT: "hsl(var(--popover))", - foreground: "hsl(var(--popover-foreground))", - }, - card: { - DEFAULT: "hsl(var(--card))", - foreground: "hsl(var(--card-foreground))", - }, - }, - borderRadius: { - xl: `calc(var(--radius) + 4px)`, - lg: `var(--radius)`, - md: `calc(var(--radius) - 2px)`, - sm: "calc(var(--radius) - 4px)", - }, - fontFamily: { - sans: ["var(--font-sans)", ...fontFamily.sans], - }, - keyframes: { - "accordion-down": { - from: { height: "0" }, - to: { height: "var(--radix-accordion-content-height)" }, - }, - "accordion-up": { - from: { height: "var(--radix-accordion-content-height)" }, - to: { height: "0" }, - }, - }, - animation: { - "accordion-down": "accordion-down 0.2s ease-out", - "accordion-up": "accordion-up 0.2s ease-out", - }, - }, - }, - plugins: [], -}; -export default config; diff --git a/packages/create-llama/templates/types/streaming/nextjs/tsconfig.json b/packages/create-llama/templates/types/streaming/nextjs/tsconfig.json deleted file mode 100644 index 40c136b82..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/tsconfig.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ], - "paths": { - "@/*": ["./*"] - }, - "forceConsistentCasingInFileNames": true - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] -} diff --git a/packages/create-llama/templates/types/streaming/nextjs/webpack.config.mjs b/packages/create-llama/templates/types/streaming/nextjs/webpack.config.mjs deleted file mode 100644 index 57fa19cfe..000000000 --- a/packages/create-llama/templates/types/streaming/nextjs/webpack.config.mjs +++ /dev/null @@ -1,10 +0,0 @@ -// webpack config must be a function in NextJS that is used to patch the default webpack config provided by NextJS, see https://nextjs.org/docs/pages/api-reference/next-config-js/webpack -export default function webpack(config) { - // See https://webpack.js.org/configuration/resolve/#resolvealias - config.resolve.alias = { - ...config.resolve.alias, - sharp$: false, - "onnxruntime-node$": false, - }; - return config; -} diff --git a/packages/create-llama/tsconfig.json b/packages/create-llama/tsconfig.json deleted file mode 100644 index 5d804d06a..000000000 --- a/packages/create-llama/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "target": "es2019", - "module": "esnext", - "moduleResolution": "node", - "strict": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "declaration": false, - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "incremental": true, - "outDir": "./lib", - "tsBuildInfoFile": "./lib/.tsbuildinfo" - }, - "include": [ - "create-app.ts", - "index.ts", - "./helpers", - "questions.ts", - "package.json" - ], - "exclude": ["dist"] -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 266021e44..e64988699 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -302,87 +302,6 @@ importers: specifier: ^1.3.1 version: 1.3.1 - packages/create-llama: - devDependencies: - '@playwright/test': - specifier: ^1.41.1 - version: 1.41.1 - '@types/async-retry': - specifier: 1.4.2 - version: 1.4.2 - '@types/ci-info': - specifier: 2.0.0 - version: 2.0.0 - '@types/cross-spawn': - specifier: 6.0.0 - version: 6.0.0 - '@types/node': - specifier: ^20.11.7 - version: 20.11.7 - '@types/prompts': - specifier: 2.0.1 - version: 2.0.1 - '@types/tar': - specifier: 6.1.5 - version: 6.1.5 - '@types/validate-npm-package-name': - specifier: 3.0.0 - version: 3.0.0 - '@vercel/ncc': - specifier: 0.38.1 - version: 0.38.1 - async-retry: - specifier: 1.3.1 - version: 1.3.1 - async-sema: - specifier: 3.0.1 - version: 3.0.1 - ci-info: - specifier: github:watson/ci-info#f43f6a1cefff47fb361c88cf4b943fdbcaafe540 - version: github.com/watson/ci-info/f43f6a1cefff47fb361c88cf4b943fdbcaafe540 - commander: - specifier: 2.20.0 - version: 2.20.0 - conf: - specifier: 10.2.0 - version: 10.2.0 - cross-spawn: - specifier: 7.0.3 - version: 7.0.3 - fast-glob: - specifier: 3.3.1 - version: 3.3.1 - got: - specifier: 10.7.0 - version: 10.7.0 - picocolors: - specifier: 1.0.0 - version: 1.0.0 - prompts: - specifier: 2.1.0 - version: 2.1.0 - rimraf: - specifier: ^5.0.5 - version: 5.0.5 - smol-toml: - specifier: ^1.1.4 - version: 1.1.4 - 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 - validate-npm-package-name: - specifier: 3.0.0 - version: 3.0.0 - wait-port: - specifier: ^1.1.0 - version: 1.1.0 - packages/env: dependencies: '@types/lodash': @@ -3609,14 +3528,6 @@ packages: dev: true optional: true - /@playwright/test@1.41.1: - resolution: {integrity: sha512-9g8EWTjiQ9yFBXc6HjCWe41msLpxEX0KhmfmPl9RPLJdfzL4F0lg2BdJ91O9azFdl11y1pmpwdjBiSxvqc+btw==} - engines: {node: '>=16'} - hasBin: true - dependencies: - playwright: 1.41.1 - dev: true - /@pnpm/config.env-replace@1.1.0: resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} engines: {node: '>=12.22.0'} @@ -3828,11 +3739,6 @@ packages: resolution: {integrity: sha512-aX5IFYWlMa7tQ8xZr3b2gtVReCvg7f3LEhjir/JAjX2bJCMVJA5tIPv30wTD4KDfcwMd7DDYY3hFDeGmOgtrZQ==} dev: false - /@sindresorhus/is@2.1.1: - resolution: {integrity: sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==} - engines: {node: '>=10'} - dev: true - /@sindresorhus/is@4.6.0: resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} @@ -4218,12 +4124,6 @@ packages: dependencies: '@types/estree': 1.0.5 - /@types/async-retry@1.4.2: - resolution: {integrity: sha512-GUDuJURF0YiJZ+CBjNQA0+vbP/VHlJbB0sFqkzsV7EcOPRfurVonXpXKAt3w8qIjM1TEzpz6hc6POocPvHOS3w==} - dependencies: - '@types/retry': 0.12.5 - dev: true - /@types/body-parser@1.19.5: resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} dependencies: @@ -4244,10 +4144,6 @@ packages: '@types/responselike': 1.0.3 dev: true - /@types/ci-info@2.0.0: - resolution: {integrity: sha512-5R2/MHILQLDCzTuhs1j4Qqq8AaKUf7Ma4KSSkCtc12+fMs47zfa34qhto9goxpyX00tQK1zxB885VCiawZ5Qhg==} - dev: true - /@types/connect-history-api-fallback@1.5.4: resolution: {integrity: sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==} dependencies: @@ -4259,12 +4155,6 @@ packages: dependencies: '@types/node': 20.11.20 - /@types/cross-spawn@6.0.0: - resolution: {integrity: sha512-evp2ZGsFw9YKprDbg8ySgC9NA15g3YgiI8ANkGmKKvvi0P2aDGYLPxQIC5qfeKNUOe3TjABVGuah6omPRpIYhg==} - dependencies: - '@types/node': 20.11.20 - dev: true - /@types/debug@4.1.12: resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} dependencies: @@ -4442,12 +4332,6 @@ packages: dependencies: undici-types: 5.26.5 - /@types/node@20.11.7: - resolution: {integrity: sha512-GPmeN1C3XAyV5uybAf4cMLWT9fDWcmQhZVtMFu7OR32WjrqGG+Wnk2V1d0bmtUyE/Zy1QJ9BxyiTih9z8Oks8A==} - dependencies: - undici-types: 5.26.5 - dev: true - /@types/normalize-package-data@2.4.4: resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} dev: true @@ -4472,10 +4356,6 @@ packages: /@types/prismjs@1.26.3: resolution: {integrity: sha512-A0D0aTXvjlqJ5ZILMz3rNfDBOx9hHxLZYv2by47Sm/pqW35zzjusrZTryatjN/Rf8Us2gZrJD+KeHbUSTux1Cw==} - /@types/prompts@2.0.1: - resolution: {integrity: sha512-AhtMcmETelF8wFDV1ucbChKhLgsc+ytXZXkNz/nnTAMSDeqsjALknEFxi7ZtLgS/G8bV2rp90LhDW5SGACimIQ==} - dev: true - /@types/prop-types@15.7.11: resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==} @@ -4542,10 +4422,6 @@ packages: /@types/retry@0.12.0: resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} - /@types/retry@0.12.5: - resolution: {integrity: sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==} - dev: true - /@types/sax@1.2.7: resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} dependencies: @@ -4585,13 +4461,6 @@ packages: dependencies: '@types/node': 20.11.20 - /@types/tar@6.1.5: - resolution: {integrity: sha512-qm2I/RlZij5RofuY7vohTpYNaYcrSQlN2MyjucQc7ZweDwaEWkdN/EeNh6e9zjK6uEm6PwjdMXkcj05BxZdX1Q==} - dependencies: - '@types/node': 20.11.20 - minipass: 4.2.8 - dev: true - /@types/triple-beam@1.3.5: resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} dev: false @@ -4602,10 +4471,6 @@ packages: /@types/unist@3.0.2: resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} - /@types/validate-npm-package-name@3.0.0: - resolution: {integrity: sha512-iFNNIrEaJH1lbPiyX+O/QyxSbKxrTjdNBVZGckt+iEL9So0hdZNBL68sOfHnt2txuUD8UJXvmKv/1DkgkebgUg==} - dev: true - /@types/webidl-conversions@7.0.2: resolution: {integrity: sha512-uNv6b/uGRLlCVmelat2rA8bcVd3k/42mV2EmjhPh6JLkd35T5bgwR/t6xy7a9MWhd9sixIeBUzhBenvk3NO+DQ==} dev: false @@ -4764,11 +4629,6 @@ packages: /@ungap/structured-clone@1.2.0: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - /@vercel/ncc@0.38.1: - resolution: {integrity: sha512-IBBb+iI2NLu4VQn3Vwldyi2QwaXt5+hTyh58ggAMoCGE6DJmPvwL3KPBWcJl1m9LYPChBLE980Jw+CS4Wokqxw==} - hasBin: true - dev: true - /@vitest/expect@1.3.1: resolution: {integrity: sha512-xofQFwIzfdmLLlHa6ag0dPV8YsnKOCP1KdAeVVh34vSjN2dcUiXYCD9htu/9eM7t8Xln4v03U9HLxLpPlsXdZw==} dependencies: @@ -5065,13 +4925,6 @@ packages: engines: {node: '>=6'} 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-escapes@6.2.0: resolution: {integrity: sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==} engines: {node: '>=14.16'} @@ -5295,16 +5148,6 @@ packages: resolution: {integrity: sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==} hasBin: true - /async-retry@1.3.1: - resolution: {integrity: sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA==} - dependencies: - retry: 0.12.0 - dev: true - - /async-sema@3.0.1: - resolution: {integrity: sha512-fKT2riE8EHAvJEfLJXZiATQWqZttjx1+tfgnVshCDrH8vlw4YC8aECe0B8MU184g+aVRFVgmfxFlKZKaozSrNw==} - dev: true - /async@3.2.5: resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} dev: false @@ -5323,11 +5166,6 @@ packages: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} - /atomically@1.7.0: - resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==} - engines: {node: '>=10.12.0'} - dev: true - /autoprefixer@10.4.17(postcss@8.4.33): resolution: {integrity: sha512-/cpVNRLSfhOtcGflT13P2794gVSgmPgTR+erw5ifnMLZb0UnSlkK4tquLmkd3BhA+nLo5tX8Cu0upUsGKvKbmg==} engines: {node: ^10 || ^12 || >=14} @@ -5614,10 +5452,6 @@ packages: dev: false optional: true - /builtins@1.0.3: - resolution: {integrity: sha512-uYBjakWipfaO/bXI7E8rq6kpwHRZK5cNYrUv2OzZSI/FvmdMyXJ2tG9dKcjEC5YHmHpUAwsargWIZNWdxb/bnQ==} - dev: true - /busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -5638,14 +5472,6 @@ packages: engines: {node: '>=8'} dev: true - /cacheable-lookup@2.0.1: - resolution: {integrity: sha512-EMMbsiOTcdngM/K6gV/OxF2x0t07+vMOWxZNSCRQMjO2MY2nhZQ6OYhOOpyQrbhqsgtvKGI7hcq6xjnA92USjg==} - engines: {node: '>=10'} - dependencies: - '@types/keyv': 3.1.4 - keyv: 4.5.4 - dev: true - /cacheable-lookup@5.0.4: resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} engines: {node: '>=10.6.0'} @@ -5841,11 +5667,6 @@ packages: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} dev: false - /chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} - dev: true - /chromadb@1.7.3(cohere-ai@7.7.5)(openai@4.26.1): resolution: {integrity: sha512-3GgvQjpqgk5C89x5EuTDaXKbfrdqYDJ5UVyLQ3ZmwxnpetNc+HhRDGjkvXa5KSvpQ3lmKoyDoqnN4tZepfFkbw==} engines: {node: '>=14.17.0'} @@ -6156,22 +5977,6 @@ packages: yargs: 17.7.2 dev: true - /conf@10.2.0: - resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==} - engines: {node: '>=12'} - dependencies: - ajv: 8.12.0 - ajv-formats: 2.1.1(ajv@8.12.0) - atomically: 1.7.0 - debounce-fn: 4.0.0 - dot-prop: 6.0.1 - env-paths: 2.2.1 - json-schema-typed: 7.0.3 - onetime: 5.1.2 - pkg-up: 3.1.0 - semver: 7.5.4 - dev: true - /config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} dependencies: @@ -6539,13 +6344,6 @@ packages: resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} dev: false - /debounce-fn@4.0.0: - resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==} - engines: {node: '>=10'} - dependencies: - mimic-fn: 3.1.0 - dev: true - /debounce@1.2.1: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} @@ -6599,13 +6397,6 @@ packages: dependencies: character-entities: 2.0.2 - /decompress-response@5.0.0: - resolution: {integrity: sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw==} - engines: {node: '>=10'} - dependencies: - mimic-response: 2.1.0 - dev: true - /decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -7048,10 +6839,6 @@ packages: underscore: 1.13.6 dev: false - /duplexer3@0.1.5: - resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} - dev: true - /duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} @@ -7137,11 +6924,6 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - /env-paths@2.2.1: - resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} - engines: {node: '>=6'} - dev: true - /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -8339,13 +8121,6 @@ packages: jsonfile: 6.1.0 universalify: 2.0.1 - /fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} - dependencies: - minipass: 3.3.6 - dev: true - /fs-monkey@1.0.5: resolution: {integrity: sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==} @@ -8362,14 +8137,6 @@ packages: /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - /fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: true - optional: true - /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -8605,29 +8372,6 @@ packages: dependencies: get-intrinsic: 1.2.2 - /got@10.7.0: - resolution: {integrity: sha512-aWTDeNw9g+XqEZNcTjMMZSy7B7yE9toWOFYip7ofFTLleJhvZwUxxTxkTpKvF+p1SAA4VHmuEy7PiHTHyq8tJg==} - engines: {node: '>=10'} - dependencies: - '@sindresorhus/is': 2.1.1 - '@szmarczak/http-timer': 4.0.6 - '@types/cacheable-request': 6.0.3 - '@types/keyv': 3.1.4 - '@types/responselike': 1.0.3 - cacheable-lookup: 2.0.1 - cacheable-request: 7.0.4 - decompress-response: 5.0.0 - duplexer3: 0.1.5 - get-stream: 5.2.0 - lowercase-keys: 2.0.0 - mimic-response: 2.1.0 - p-cancelable: 2.1.1 - p-event: 4.2.0 - responselike: 2.0.1 - to-readable-stream: 2.1.0 - type-fest: 0.10.0 - dev: true - /got@11.8.6: resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} engines: {node: '>=10.19.0'} @@ -9664,10 +9408,6 @@ packages: /json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - /json-schema-typed@7.0.3: - resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==} - dev: true - /json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -10773,11 +10513,6 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - /mimic-fn@3.1.0: - resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==} - engines: {node: '>=8'} - dev: true - /mimic-fn@4.0.0: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} @@ -10788,11 +10523,6 @@ packages: engines: {node: '>=4'} dev: true - /mimic-response@2.1.0: - resolution: {integrity: sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==} - engines: {node: '>=8'} - dev: true - /mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -10841,31 +10571,11 @@ packages: /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - /minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} - dependencies: - yallist: 4.0.0 - dev: true - - /minipass@4.2.8: - resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} - engines: {node: '>=8'} - dev: true - /minipass@5.0.0: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} dev: true - /minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} - dependencies: - minipass: 3.3.6 - yallist: 4.0.0 - dev: true - /mixme@0.5.10: resolution: {integrity: sha512-5H76ANWinB1H3twpJ6JY8uvAtpmFvHNArpilJAjXRKXSDDLPIMoZArw5SH0q9z+lLs8IrMw7Q2VWpWimFKFT1Q==} engines: {node: '>= 8.0.0'} @@ -10875,12 +10585,6 @@ packages: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} dev: false - /mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - dev: true - /mlly@1.6.0: resolution: {integrity: sha512-YOvg9hfYQmnaB56Yb+KrJE2u0Yzz5zR+sLejEvF4fzwzV1Al6hkf2vyHTwqCRyv0hCi9rVCqVoXpyYevQIRwLQ==} dependencies: @@ -11473,13 +11177,6 @@ packages: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} engines: {node: '>=12.20'} - /p-event@4.2.0: - resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==} - engines: {node: '>=8'} - dependencies: - p-timeout: 3.2.0 - dev: true - /p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} @@ -11560,13 +11257,6 @@ packages: '@types/retry': 0.12.0 retry: 0.13.1 - /p-timeout@3.2.0: - resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} - engines: {node: '>=8'} - dependencies: - p-finally: 1.0.0 - dev: true - /p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -11890,22 +11580,6 @@ packages: resolution: {integrity: sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==} dev: false - /playwright-core@1.41.1: - resolution: {integrity: sha512-/KPO5DzXSMlxSX77wy+HihKGOunh3hqndhqeo/nMxfigiKzogn8kfL0ZBDu0L1RKgan5XHCPmn6zXd2NUJgjhg==} - engines: {node: '>=16'} - hasBin: true - dev: true - - /playwright@1.41.1: - resolution: {integrity: sha512-gdZAWG97oUnbBdRL3GuBvX3nDDmUOuqzV/D24dytqlKt+eI5KbwusluZRGljx1YoJKZ2NRPaeWiFTeGZO7SosQ==} - engines: {node: '>=16'} - hasBin: true - dependencies: - playwright-core: 1.41.1 - optionalDependencies: - fsevents: 2.3.2 - dev: true - /pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -12534,14 +12208,6 @@ packages: dev: false optional: true - /prompts@2.1.0: - resolution: {integrity: sha512-+x5TozgqYdOwWsQFZizE/Tra3fKvAoy037kOyU6cgz84n8f6zxngLOV4O32kTwt9FcLCxAqw0P/c8rOr9y+Gfg==} - engines: {node: '>= 6'} - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - dev: true - /prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -13063,26 +12729,12 @@ packages: unicode-match-property-ecmascript: 2.0.0 unicode-match-property-value-ecmascript: 2.1.0 - /registry-auth-token@3.3.2: - resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==} - dependencies: - rc: 1.2.8 - safe-buffer: 5.2.1 - dev: true - /registry-auth-token@5.0.2: resolution: {integrity: sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==} engines: {node: '>=14'} dependencies: '@pnpm/npm-conf': 2.2.2 - /registry-url@3.1.0: - resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==} - engines: {node: '>=0.10.0'} - dependencies: - rc: 1.2.8 - dev: true - /registry-url@6.0.1: resolution: {integrity: sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==} engines: {node: '>=12'} @@ -13308,11 +12960,6 @@ packages: signal-exit: 3.0.7 dev: true - /retry@0.12.0: - resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} - engines: {node: '>= 4'} - dev: true - /retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} @@ -13330,14 +12977,6 @@ packages: dependencies: glob: 7.2.3 - /rimraf@5.0.5: - resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==} - engines: {node: '>=14'} - hasBin: true - dependencies: - glob: 10.3.10 - dev: true - /rollup@4.12.0: resolution: {integrity: sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -13796,11 +13435,6 @@ packages: yargs: 15.4.1 dev: true - /smol-toml@1.1.4: - resolution: {integrity: sha512-Y0OT8HezWsTNeEOSVxDnKOW/AyNXHQ4BwJNbAXlLTF5wWsBvrcHhIkE5Rf8kQMLmgf7nDX3PVOlgC6/Aiggu3Q==} - engines: {node: '>= 18', pnpm: '>= 8'} - dev: true - /sockjs@0.3.24: resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} dependencies: @@ -14268,14 +13902,6 @@ 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'} @@ -14340,31 +13966,11 @@ packages: streamx: 2.15.6 dev: false - /tar@6.1.15: - resolution: {integrity: sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==} - engines: {node: '>=10'} - dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 - dev: true - /term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} dev: true - /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.10(webpack@5.90.0): resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} @@ -14476,11 +14082,6 @@ packages: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} - /to-readable-stream@2.1.0: - resolution: {integrity: sha512-o3Qa6DGg1CEXshSdvWNX2sN4QHqg03SPq7U6jPXRahlQdl5dK8oXjkU/2/sGrnOZKeGV1zLSO8qPwyKklPPE7w==} - engines: {node: '>=8'} - dev: true - /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -14760,11 +14361,6 @@ packages: engines: {node: '>=4'} dev: true - /type-fest@0.10.0: - resolution: {integrity: sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw==} - engines: {node: '>=8'} - dev: true - /type-fest@0.13.1: resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} engines: {node: '>=10'} @@ -15038,13 +14634,6 @@ packages: picocolors: 1.0.0 dev: false - /update-check@1.5.4: - resolution: {integrity: sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==} - dependencies: - registry-auth-token: 3.3.2 - registry-url: 3.1.0 - dev: true - /update-notifier@6.0.2: resolution: {integrity: sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==} engines: {node: '>=14.16'} @@ -15118,12 +14707,6 @@ packages: spdx-expression-parse: 3.0.1 dev: true - /validate-npm-package-name@3.0.0: - resolution: {integrity: sha512-M6w37eVCMMouJ9V/sdPGnC5H4uDr73/+xdq0FBLO3TFFX1+7wiUY6Es328NN+y43tmY+doUdN9g9J21vqB7iLw==} - dependencies: - builtins: 1.0.3 - dev: true - /value-equal@1.0.1: resolution: {integrity: sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==} @@ -15269,18 +14852,6 @@ packages: resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} dev: true - /wait-port@1.1.0: - resolution: {integrity: sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==} - engines: {node: '>=10'} - hasBin: true - dependencies: - chalk: 4.1.2 - commander: 9.5.0 - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: true - /walkdir@0.4.1: resolution: {integrity: sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==} engines: {node: '>=6.0.0'} @@ -15857,9 +15428,3 @@ packages: /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - - github.com/watson/ci-info/f43f6a1cefff47fb361c88cf4b943fdbcaafe540: - resolution: {tarball: https://codeload.github.com/watson/ci-info/tar.gz/f43f6a1cefff47fb361c88cf4b943fdbcaafe540} - name: ci-info - version: 2.0.0 - dev: true diff --git a/tsconfig.json b/tsconfig.json index d95d2781d..029b13bda 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -35,12 +35,6 @@ { "path": "./packages/env/tsconfig.json" }, - { - "path": "./packages/create-llama" - }, - { - "path": "./packages/create-llama/e2e" - }, { "path": "./examples" }, -- GitLab