├── .codespellignore ├── .dockerignore ├── .editorconfig ├── .github └── workflows │ ├── ci.yml │ └── e2e.yml ├── .gitignore ├── .prettierignore ├── .yarnrc.yml ├── LICENSE ├── README.md ├── eslint.config.js ├── package.json ├── scripts ├── cleanup-tests.sh ├── run-all-e2e-local.sh ├── run-e2e-local.sh ├── test-build.sh └── test-create-command.sh ├── src ├── gitignore.ts └── index.ts ├── static └── demo.gif ├── templates ├── memory-agent │ ├── README.md │ ├── configuration.ts │ ├── graph.ts │ ├── prompts.ts │ ├── state.ts │ ├── static │ │ ├── memories.png │ │ └── memory_graph.png │ ├── tests │ │ ├── integration │ │ │ └── graph.int.test.ts │ │ └── unit │ │ │ ├── configuration.test.ts │ │ │ └── graph.test.ts │ ├── tools.ts │ └── utils.ts ├── monorepo │ ├── .gitignore │ ├── README.md │ ├── apps │ │ └── agents │ │ │ ├── .gitignore │ │ │ ├── .prettierrc │ │ │ ├── README.md │ │ │ ├── eslint.config.js │ │ │ ├── package.json │ │ │ ├── tsconfig.json │ │ │ └── turbo.json │ ├── langgraph.json │ ├── package.json │ ├── tsconfig.json │ └── turbo.json ├── nextjs │ ├── .dockerignore │ ├── .env.example │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── components.json │ ├── eslint.config.js │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.mjs │ ├── src │ │ ├── app │ │ │ ├── api │ │ │ │ └── [..._path] │ │ │ │ │ └── route.ts │ │ │ ├── favicon.ico │ │ │ ├── globals.css │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── components │ │ │ ├── icons │ │ │ │ ├── github.tsx │ │ │ │ └── langgraph.tsx │ │ │ ├── thread │ │ │ │ ├── agent-inbox │ │ │ │ │ ├── components │ │ │ │ │ │ ├── inbox-item-input.tsx │ │ │ │ │ │ ├── state-view.tsx │ │ │ │ │ │ ├── thread-actions-view.tsx │ │ │ │ │ │ ├── thread-id.tsx │ │ │ │ │ │ └── tool-call-table.tsx │ │ │ │ │ ├── hooks │ │ │ │ │ │ └── use-interrupted-actions.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── history │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── markdown-styles.css │ │ │ │ ├── markdown-text.tsx │ │ │ │ ├── messages │ │ │ │ │ ├── ai.tsx │ │ │ │ │ ├── generic-interrupt.tsx │ │ │ │ │ ├── human.tsx │ │ │ │ │ ├── shared.tsx │ │ │ │ │ └── tool-calls.tsx │ │ │ │ ├── syntax-highlighter.tsx │ │ │ │ ├── tooltip-icon-button.tsx │ │ │ │ └── utils.ts │ │ │ └── ui │ │ │ │ ├── avatar.tsx │ │ │ │ ├── button.tsx │ │ │ │ ├── card.tsx │ │ │ │ ├── input.tsx │ │ │ │ ├── label.tsx │ │ │ │ ├── password-input.tsx │ │ │ │ ├── separator.tsx │ │ │ │ ├── sheet.tsx │ │ │ │ ├── skeleton.tsx │ │ │ │ ├── sonner.tsx │ │ │ │ ├── switch.tsx │ │ │ │ ├── textarea.tsx │ │ │ │ └── tooltip.tsx │ │ ├── hooks │ │ │ └── useMediaQuery.tsx │ │ ├── lib │ │ │ ├── agent-inbox-interrupt.ts │ │ │ ├── api-key.tsx │ │ │ ├── ensure-tool-responses.ts │ │ │ └── utils.ts │ │ └── providers │ │ │ ├── Stream.tsx │ │ │ ├── Thread.tsx │ │ │ └── client.ts │ ├── tailwind.config.js │ ├── tsconfig.json │ └── turbo.json ├── react-agent │ ├── README.md │ ├── configuration.ts │ ├── graph.ts │ ├── prompts.ts │ ├── static │ │ └── studio_ui.png │ ├── tests │ │ ├── integration │ │ │ └── graph.int.test.ts │ │ └── unit │ │ │ └── graph.test.ts │ ├── tools.ts │ └── utils.ts ├── research-agent │ ├── README.md │ ├── index-graph │ │ ├── configuration.ts │ │ ├── graph.ts │ │ └── state.ts │ ├── retrieval-graph │ │ ├── configuration.ts │ │ ├── graph.ts │ │ ├── prompts.ts │ │ ├── researcher-graph │ │ │ ├── graph.ts │ │ │ └── state.ts │ │ ├── state.ts │ │ └── utils.ts │ ├── sample_docs.json │ ├── shared │ │ ├── configuration.ts │ │ ├── retrieval.ts │ │ ├── state.ts │ │ └── utils.ts │ └── static │ │ └── studio_ui.png ├── retrieval-agent │ ├── README.md │ ├── configuration.ts │ ├── graph.ts │ ├── index_graph.ts │ ├── prompts.ts │ ├── retrieval.ts │ ├── state.ts │ ├── static │ │ └── studio_ui.png │ └── utils.ts └── vite │ ├── .dockerignore │ ├── .env.example │ ├── .gitignore │ ├── .prettierrc │ ├── README.md │ ├── components.json │ ├── eslint.config.js │ ├── index.html │ ├── package.json │ ├── public │ └── logo.svg │ ├── src │ ├── App.css │ ├── App.tsx │ ├── components │ │ ├── icons │ │ │ ├── github.tsx │ │ │ └── langgraph.tsx │ │ ├── thread │ │ │ ├── agent-inbox │ │ │ │ ├── components │ │ │ │ │ ├── inbox-item-input.tsx │ │ │ │ │ ├── state-view.tsx │ │ │ │ │ ├── thread-actions-view.tsx │ │ │ │ │ ├── thread-id.tsx │ │ │ │ │ └── tool-call-table.tsx │ │ │ │ ├── hooks │ │ │ │ │ └── use-interrupted-actions.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── types.ts │ │ │ │ └── utils.ts │ │ │ ├── history │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ ├── markdown-styles.css │ │ │ ├── markdown-text.tsx │ │ │ ├── messages │ │ │ │ ├── ai.tsx │ │ │ │ ├── generic-interrupt.tsx │ │ │ │ ├── human.tsx │ │ │ │ ├── shared.tsx │ │ │ │ └── tool-calls.tsx │ │ │ ├── syntax-highlighter.tsx │ │ │ ├── tooltip-icon-button.tsx │ │ │ └── utils.ts │ │ └── ui │ │ │ ├── avatar.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── password-input.tsx │ │ │ ├── separator.tsx │ │ │ ├── sheet.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── sonner.tsx │ │ │ ├── switch.tsx │ │ │ ├── textarea.tsx │ │ │ └── tooltip.tsx │ ├── hooks │ │ └── useMediaQuery.tsx │ ├── index.css │ ├── lib │ │ ├── agent-inbox-interrupt.ts │ │ ├── api-key.tsx │ │ ├── ensure-tool-responses.ts │ │ └── utils.ts │ ├── main.tsx │ ├── providers │ │ ├── Stream.tsx │ │ ├── Thread.tsx │ │ └── client.ts │ └── vite-env.d.ts │ ├── tailwind.config.js │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.node.json │ ├── turbo.json │ └── vite.config.ts ├── tsconfig.json └── yarn.lock /.codespellignore: -------------------------------------------------------------------------------- 1 | IST 2 | afterAll 3 | devlop 4 | trough -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{js,json,yml}] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # Run formatting on all PRs 2 | 3 | name: CI 4 | 5 | on: 6 | push: 7 | branches: ["main"] 8 | pull_request: 9 | workflow_dispatch: # Allows triggering the workflow manually in GitHub UI 10 | 11 | # If another push to the same PR or branch happens while this workflow is still running, 12 | # cancel the earlier run in favor of the next run. 13 | # 14 | # There's no point in testing an outdated version of the code. GitHub only allows 15 | # a limited number of job runners to be active at the same time, so it's better to cancel 16 | # pointless jobs early so that more useful jobs can run sooner. 17 | concurrency: 18 | group: ${{ github.workflow }}-${{ github.ref }} 19 | cancel-in-progress: true 20 | 21 | jobs: 22 | format: 23 | name: Check formatting 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - name: Enable Corepack 29 | run: corepack enable 30 | 31 | - name: Use Node.js 18.x 32 | uses: actions/setup-node@v3 33 | with: 34 | node-version: 18.x 35 | cache: "yarn" 36 | 37 | - name: Install dependencies 38 | run: yarn install --immutable --mode=skip-build 39 | 40 | - name: Check formatting 41 | run: yarn format:check 42 | 43 | lint: 44 | name: Check linting 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v4 48 | 49 | - name: Enable Corepack 50 | run: corepack enable 51 | 52 | - name: Use Node.js 18.x 53 | uses: actions/setup-node@v3 54 | with: 55 | node-version: 18.x 56 | cache: "yarn" 57 | 58 | - name: Install dependencies 59 | run: yarn install --immutable --mode=skip-build 60 | 61 | - name: Check linting 62 | run: yarn run lint 63 | 64 | readme-spelling: 65 | name: Check README spelling 66 | runs-on: ubuntu-latest 67 | steps: 68 | - uses: actions/checkout@v4 69 | - uses: codespell-project/actions-codespell@v2 70 | with: 71 | ignore_words_file: .codespellignore 72 | path: README.md 73 | 74 | check-spelling: 75 | name: Check code spelling 76 | runs-on: ubuntu-latest 77 | steps: 78 | - uses: actions/checkout@v4 79 | - uses: codespell-project/actions-codespell@v2 80 | with: 81 | ignore_words_file: .codespellignore 82 | path: src 83 | -------------------------------------------------------------------------------- /.github/workflows/e2e.yml: -------------------------------------------------------------------------------- 1 | name: E2E Tests 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | workflow_dispatch: # Allows triggering the workflow manually in GitHub UI 8 | 9 | # If another push to the same PR or branch happens while this workflow is still running, 10 | # cancel the earlier run in favor of the next run. 11 | # 12 | # There's no point in testing an outdated version of the code. GitHub only allows 13 | # a limited number of job runners to be active at the same time, so it's better to cancel 14 | # pointless jobs early so that more useful jobs can run sooner. 15 | concurrency: 16 | group: ${{ github.workflow }}-${{ github.ref }} 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | test-create-command: 21 | runs-on: ubuntu-latest 22 | strategy: 23 | matrix: 24 | node-version: [18.x, 20.x, 22.x] 25 | package-manager: [npm, yarn, pnpm] 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | 30 | - name: Enable Corepack 31 | run: corepack enable 32 | 33 | - name: Set up Node.js ${{ matrix.node-version }} 34 | uses: actions/setup-node@v4 35 | with: 36 | node-version: ${{ matrix.node-version }} 37 | cache: "yarn" 38 | 39 | - name: Setup pnpm 40 | if: matrix.package-manager == 'pnpm' 41 | uses: pnpm/action-setup@v2 42 | with: 43 | version: 10.5.1 44 | 45 | - name: Install dependencies 46 | run: yarn install --frozen-lockfile 47 | 48 | - name: Build package 49 | run: yarn build 50 | 51 | - name: Create test directory 52 | run: mkdir -p /tmp/test-${{ matrix.package-manager }} 53 | 54 | - name: Run create-agent-chat-app command 55 | run: ./scripts/test-create-command.sh ${{ matrix.package-manager }} 56 | 57 | - name: Test build in created project 58 | run: ./scripts/test-build.sh ${{ matrix.package-manager }} 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | index.js 3 | gitignore.js 4 | .yarn/* 5 | !.yarn/patches 6 | !.yarn/plugins 7 | !.yarn/releases 8 | !.yarn/sdks 9 | !.yarn/versions 10 | yarn-error.log 11 | 12 | .turbo 13 | **/.turbo 14 | **/.eslintcache 15 | 16 | .env 17 | .env.full 18 | .env.quickstart 19 | .ipynb_checkpoints 20 | 21 | 22 | # LangGraph API 23 | .langgraph_api 24 | 25 | __pycache__/ 26 | .mypy_cache/ 27 | .ruff_cache/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .env 4 | coverage 5 | index.js 6 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Brace Sproul 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import tseslint from "@typescript-eslint/eslint-plugin"; 3 | import tsparser from "@typescript-eslint/parser"; 4 | import importPlugin from "eslint-plugin-import"; 5 | import noInstanceofPlugin from "eslint-plugin-no-instanceof"; 6 | import prettierConfig from "eslint-config-prettier"; 7 | import globals from "globals"; 8 | 9 | export default [ 10 | js.configs.recommended, 11 | prettierConfig, 12 | { 13 | files: ["**/*.ts", "**/*.tsx"], 14 | plugins: { 15 | "@typescript-eslint": tseslint, 16 | import: importPlugin, 17 | "no-instanceof": noInstanceofPlugin, 18 | }, 19 | languageOptions: { 20 | parser: tsparser, 21 | parserOptions: { 22 | ecmaVersion: 2021, 23 | sourceType: "module", 24 | project: "./tsconfig.json", 25 | tsconfigRootDir: ".", 26 | }, 27 | globals: { 28 | ...globals.node, 29 | }, 30 | }, 31 | ignores: [ 32 | "node_modules/**", 33 | "dist/**", 34 | "dist-cjs/**", 35 | ".eslintrc.cjs", 36 | "scripts/**", 37 | "*.d.ts", 38 | ], 39 | rules: { 40 | "@typescript-eslint/explicit-module-boundary-types": 0, 41 | "@typescript-eslint/no-empty-function": 0, 42 | "@typescript-eslint/no-shadow": 0, 43 | "@typescript-eslint/no-empty-interface": 0, 44 | "no-unused-vars": 0, 45 | "@typescript-eslint/no-use-before-define": ["error", "nofunc"], 46 | "@typescript-eslint/no-unused-vars": [ 47 | "warn", 48 | { 49 | args: "none", 50 | varsIgnorePattern: "^_", 51 | argsIgnorePattern: "^_", 52 | caughtErrorsIgnorePattern: "^_", 53 | destructuredArrayIgnorePattern: "^_", 54 | ignoreRestSiblings: true, 55 | }, 56 | ], 57 | "@typescript-eslint/no-floating-promises": "error", 58 | "@typescript-eslint/no-misused-promises": "error", 59 | "@typescript-eslint/no-explicit-any": 0, 60 | camelcase: 0, 61 | "class-methods-use-this": 0, 62 | "import/extensions": [2, "ignorePackages"], 63 | "import/no-extraneous-dependencies": [ 64 | "error", 65 | { devDependencies: ["**/*.test.ts"] }, 66 | ], 67 | "import/no-unresolved": 0, 68 | "import/prefer-default-export": 0, 69 | "keyword-spacing": "error", 70 | "max-classes-per-file": 0, 71 | "max-len": 0, 72 | "no-await-in-loop": 0, 73 | "no-bitwise": 0, 74 | "no-console": 0, 75 | "no-restricted-syntax": 0, 76 | "no-shadow": 0, 77 | "no-continue": 0, 78 | "no-underscore-dangle": 0, 79 | "no-use-before-define": 0, 80 | "no-useless-constructor": 0, 81 | "no-return-await": 0, 82 | "consistent-return": 0, 83 | "no-else-return": 0, 84 | "new-cap": ["error", { properties: false, capIsNew: false }], 85 | }, 86 | }, 87 | ]; 88 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-agent-chat-app", 3 | "version": "0.1.6", 4 | "description": "Create a LangGraph chat app with one command", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/langchain-ai/create-agent-chat-app.git" 8 | }, 9 | "homepage": "https://github.com/langchain-ai/create-agent-chat-app/blob/main/README.md", 10 | "packageManager": "yarn@3.5.1", 11 | "main": "index.js", 12 | "author": "Brace Sproul", 13 | "license": "MIT", 14 | "type": "module", 15 | "bin": "index.js", 16 | "engines": { 17 | "node": ">=16.0.0" 18 | }, 19 | "files": [ 20 | "index.js", 21 | "gitignore.js", 22 | "templates/**" 23 | ], 24 | "scripts": { 25 | "build": "tsc ./src/*.ts --esModuleInterop --target es2020 --module esnext --moduleResolution node --outDir .", 26 | "prepublishOnly": "npm run build", 27 | "clean": "rm -rf src/index.js", 28 | "format": "prettier --write . --ignore-path .prettierignore", 29 | "lint": "eslint 'src/**/*.{ts,tsx}'", 30 | "lint:fix": "eslint 'src/**/*.{ts,tsx}' --fix", 31 | "format:check": "prettier --check . --ignore-path .prettierignore", 32 | "e2e": "./scripts/run-all-e2e-local.sh", 33 | "e2e:npm": "./scripts/run-e2e-local.sh npm cleanup", 34 | "e2e:yarn": "./scripts/run-e2e-local.sh yarn cleanup", 35 | "e2e:pnpm": "./scripts/run-e2e-local.sh pnpm cleanup", 36 | "e2e:cleanup": "./scripts/cleanup-tests.sh" 37 | }, 38 | "dependencies": { 39 | "@clack/prompts": "^0.10.0", 40 | "chalk": "^5.3.0", 41 | "commander": "^13.1.0", 42 | "fs-extra": "^11.2.0" 43 | }, 44 | "devDependencies": { 45 | "@eslint/eslintrc": "^3.3.0", 46 | "@eslint/js": "^9.22.0", 47 | "@jest/globals": "^29.7.0", 48 | "@tsconfig/recommended": "^1.0.7", 49 | "@types/fs-extra": "^11.0.4", 50 | "@types/jest": "^29.5.0", 51 | "@types/node": "^22.10.6", 52 | "@types/prompts": "^2.4.9", 53 | "@typescript-eslint/eslint-plugin": "^8.26.1", 54 | "@typescript-eslint/parser": "^8.26.1", 55 | "dotenv": "^16.4.7", 56 | "eslint": "^9.19.0", 57 | "eslint-config-prettier": "^10.1.1", 58 | "eslint-plugin-import": "^2.31.0", 59 | "eslint-plugin-no-instanceof": "^1.0.1", 60 | "eslint-plugin-prettier": "^5.2.3", 61 | "globals": "^16.0.0", 62 | "jest": "^29.7.0", 63 | "prettier": "^3.3.3", 64 | "ts-jest": "^29.1.0", 65 | "tsx": "^4.19.2", 66 | "typescript": "^5.3.3" 67 | }, 68 | "keywords": [ 69 | "langgraph", 70 | "starter", 71 | "template", 72 | "create-app", 73 | "vite", 74 | "react", 75 | "langchain", 76 | "openai" 77 | ] 78 | } 79 | -------------------------------------------------------------------------------- /scripts/cleanup-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "Cleaning up test directories..." 5 | 6 | # Clean up test directories for all package managers 7 | rm -rf /tmp/test-npm /tmp/test-yarn /tmp/test-pnpm 8 | 9 | echo "Cleanup completed successfully!" 10 | -------------------------------------------------------------------------------- /scripts/run-all-e2e-local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 5 | 6 | # Run tests for each package manager 7 | echo "Running E2E tests for all package managers..." 8 | 9 | # Test npm 10 | echo "=== Testing with npm ===" 11 | "$SCRIPT_DIR/run-e2e-local.sh" npm 12 | 13 | # Test yarn 14 | echo "=== Testing with yarn ===" 15 | "$SCRIPT_DIR/run-e2e-local.sh" yarn 16 | 17 | # Test pnpm 18 | echo "=== Testing with pnpm ===" 19 | "$SCRIPT_DIR/run-e2e-local.sh" pnpm 20 | 21 | # Clean up after all tests 22 | echo "Cleaning up test directories..." 23 | "$SCRIPT_DIR/cleanup-tests.sh" 24 | 25 | echo "All E2E tests completed successfully!" 26 | -------------------------------------------------------------------------------- /scripts/run-e2e-local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Check if package manager is provided 5 | if [ -z "$1" ]; then 6 | echo "Usage: $0 [cleanup]" 7 | echo " package-manager: npm, yarn, or pnpm" 8 | echo " cleanup: add 'cleanup' as second argument to clean up test directories after tests" 9 | exit 1 10 | fi 11 | 12 | PACKAGE_MANAGER=$1 13 | CLEANUP=$2 14 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 15 | PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" 16 | 17 | # Build the package first 18 | echo "Building package..." 19 | cd "$PROJECT_ROOT" 20 | yarn build 21 | 22 | # Run the test scripts 23 | echo "Running E2E tests for $PACKAGE_MANAGER..." 24 | "$SCRIPT_DIR/test-create-command.sh" "$PACKAGE_MANAGER" 25 | "$SCRIPT_DIR/test-build.sh" "$PACKAGE_MANAGER" 26 | 27 | # Clean up if requested 28 | if [ "$CLEANUP" = "cleanup" ]; then 29 | echo "Cleaning up test directories..." 30 | "$SCRIPT_DIR/cleanup-tests.sh" 31 | fi 32 | 33 | echo "E2E tests for $PACKAGE_MANAGER completed successfully!" 34 | -------------------------------------------------------------------------------- /scripts/test-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Get the package manager from the first argument 5 | PACKAGE_MANAGER=$1 6 | TEST_DIR="/tmp/test-${PACKAGE_MANAGER}" 7 | PROJECT_NAME="test-agent-chat-app" 8 | PROJECT_DIR="${TEST_DIR}/${PROJECT_NAME}" 9 | 10 | echo "Testing build in created project with package manager: ${PACKAGE_MANAGER}" 11 | 12 | # Navigate to the project directory 13 | cd "${PROJECT_DIR}" 14 | 15 | # Run the build command based on the package manager 16 | echo "Building project..." 17 | case "${PACKAGE_MANAGER}" in 18 | npm) 19 | npm run build 20 | ;; 21 | yarn) 22 | yarn build 23 | ;; 24 | pnpm) 25 | pnpm build 26 | ;; 27 | *) 28 | echo "Unsupported package manager: ${PACKAGE_MANAGER}" 29 | exit 1 30 | ;; 31 | esac 32 | 33 | # Check if the build was successful 34 | if [ $? -eq 0 ]; then 35 | echo "Build completed successfully!" 36 | else 37 | echo "Build failed!" 38 | exit 1 39 | fi 40 | -------------------------------------------------------------------------------- /scripts/test-create-command.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # Get the package manager from the first argument 5 | PACKAGE_MANAGER=$1 6 | TEST_DIR="/tmp/test-${PACKAGE_MANAGER}" 7 | PROJECT_NAME="test-agent-chat-app" 8 | PROJECT_DIR="${TEST_DIR}/${PROJECT_NAME}" 9 | 10 | echo "Testing create-agent-chat-app with package manager: ${PACKAGE_MANAGER}" 11 | 12 | # Clean up any previous test directory 13 | rm -rf "${PROJECT_DIR}" 14 | mkdir -p "${TEST_DIR}" 15 | cd "${TEST_DIR}" 16 | 17 | # Determine the project root in local environment or CI 18 | PROJECT_ROOT=${GITHUB_WORKSPACE:-$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd .. && pwd)} 19 | 20 | # Run the create command with the specified package manager 21 | echo "Running create-agent-chat-app command..." 22 | cd "${TEST_DIR}" 23 | 24 | # Run the command directly using node with the local index.js file 25 | echo "Using local package from ${PROJECT_ROOT}" 26 | node "${PROJECT_ROOT}/index.js" -Y --project-name="${PROJECT_NAME}" --package-manager="${PACKAGE_MANAGER}" 27 | 28 | # Verify the project was created 29 | if [ ! -d "${PROJECT_DIR}" ]; then 30 | echo "Error: Project directory was not created at ${PROJECT_DIR}" 31 | exit 1 32 | fi 33 | 34 | echo "Project created successfully at ${PROJECT_DIR}" 35 | -------------------------------------------------------------------------------- /src/gitignore.ts: -------------------------------------------------------------------------------- 1 | export const BASE_GITIGNORE = `# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | **/node_modules 6 | /.pnp 7 | .pnp.js 8 | .yarn/install-state.gz 9 | .yarn/cache 10 | 11 | # testing 12 | /coverage 13 | 14 | # next.js 15 | /.next/ 16 | /out/ 17 | 18 | # production 19 | /build 20 | /dist 21 | **/dist 22 | .turbo/ 23 | 24 | # misc 25 | .DS_Store 26 | *.pem 27 | 28 | # debug 29 | npm-debug.log* 30 | yarn-debug.log* 31 | yarn-error.log* 32 | 33 | # local env files 34 | .env*.local 35 | .env 36 | 37 | # vercel 38 | .vercel 39 | 40 | # typescript 41 | *.tsbuildinfo 42 | next-env.d.ts 43 | 44 | credentials.json 45 | 46 | # LangGraph API 47 | .langgraph_api 48 | `; 49 | 50 | export const NEXTJS_GITIGNORE = `# Logs 51 | logs 52 | *.log 53 | npm-debug.log* 54 | yarn-debug.log* 55 | yarn-error.log* 56 | pnpm-debug.log* 57 | lerna-debug.log* 58 | 59 | node_modules 60 | dist 61 | dist-ssr 62 | *.local 63 | 64 | # Editor directories and files 65 | .vscode/* 66 | !.vscode/extensions.json 67 | .idea 68 | .DS_Store 69 | *.suo 70 | *.ntvs* 71 | *.njsproj 72 | *.sln 73 | *.sw? 74 | 75 | # LangGraph API 76 | .langgraph_api 77 | .env 78 | .next/ 79 | next-env.d.ts`; 80 | 81 | export const VITE_GITIGNORE = `# Logs 82 | logs 83 | *.log 84 | npm-debug.log* 85 | yarn-debug.log* 86 | yarn-error.log* 87 | pnpm-debug.log* 88 | lerna-debug.log* 89 | 90 | node_modules 91 | dist 92 | dist-ssr 93 | *.local 94 | 95 | # Editor directories and files 96 | .vscode/* 97 | !.vscode/extensions.json 98 | .idea 99 | .DS_Store 100 | *.suo 101 | *.ntvs* 102 | *.njsproj 103 | *.sln 104 | *.sw? 105 | 106 | # LangGraph API 107 | .langgraph_api 108 | .env`; 109 | -------------------------------------------------------------------------------- /static/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langchain-ai/create-agent-chat-app/38c4631e6eaa806dc78b1636688ae1520409d93a/static/demo.gif -------------------------------------------------------------------------------- /templates/memory-agent/configuration.ts: -------------------------------------------------------------------------------- 1 | // Define the configurable parameters for the agent 2 | 3 | import { Annotation, LangGraphRunnableConfig } from "@langchain/langgraph"; 4 | import { SYSTEM_PROMPT } from "./prompts.js"; 5 | 6 | export const ConfigurationAnnotation = Annotation.Root({ 7 | userId: Annotation(), 8 | model: Annotation(), 9 | systemPrompt: Annotation(), 10 | }); 11 | 12 | export type Configuration = typeof ConfigurationAnnotation.State; 13 | 14 | export function ensureConfiguration(config?: LangGraphRunnableConfig) { 15 | const configurable = config?.configurable || {}; 16 | return { 17 | userId: configurable?.userId || "default", 18 | model: configurable?.model || "anthropic/claude-3-7-sonnet-latest", 19 | systemPrompt: configurable?.systemPrompt || SYSTEM_PROMPT, 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /templates/memory-agent/graph.ts: -------------------------------------------------------------------------------- 1 | // Main graph 2 | import { 3 | LangGraphRunnableConfig, 4 | START, 5 | StateGraph, 6 | END, 7 | } from "@langchain/langgraph"; 8 | import { BaseMessage, AIMessage } from "@langchain/core/messages"; 9 | import { initChatModel } from "langchain/chat_models/universal"; 10 | import { initializeTools } from "./tools.js"; 11 | import { 12 | ConfigurationAnnotation, 13 | ensureConfiguration, 14 | } from "./configuration.js"; 15 | import { GraphAnnotation } from "./state.js"; 16 | import { getStoreFromConfigOrThrow, splitModelAndProvider } from "./utils.js"; 17 | 18 | async function callModel( 19 | state: typeof GraphAnnotation.State, 20 | config: LangGraphRunnableConfig, 21 | ): Promise<{ messages: BaseMessage[] }> { 22 | const llm = await initChatModel(); 23 | const store = getStoreFromConfigOrThrow(config); 24 | const configurable = ensureConfiguration(config); 25 | const memories = await store.search(["memories", configurable.userId], { 26 | limit: 10, 27 | }); 28 | 29 | let formatted = 30 | memories 31 | ?.map((mem) => `[${mem.key}]: ${JSON.stringify(mem.value)}`) 32 | ?.join("\n") || ""; 33 | if (formatted) { 34 | formatted = `\n\n${formatted}\n`; 35 | } 36 | 37 | const sys = configurable.systemPrompt 38 | .replace("{user_info}", formatted) 39 | .replace("{time}", new Date().toISOString()); 40 | 41 | const tools = initializeTools(config); 42 | const boundLLM = llm.bind({ 43 | tools: tools, 44 | tool_choice: "auto", 45 | }); 46 | 47 | const result = await boundLLM.invoke( 48 | [{ role: "system", content: sys }, ...state.messages], 49 | { 50 | configurable: splitModelAndProvider(configurable.model), 51 | }, 52 | ); 53 | 54 | return { messages: [result] }; 55 | } 56 | 57 | async function storeMemory( 58 | state: typeof GraphAnnotation.State, 59 | config: LangGraphRunnableConfig, 60 | ): Promise<{ messages: BaseMessage[] }> { 61 | const lastMessage = state.messages[state.messages.length - 1] as AIMessage; 62 | const toolCalls = lastMessage.tool_calls || []; 63 | 64 | const tools = initializeTools(config); 65 | const upsertMemoryTool = tools[0]; 66 | 67 | const savedMemories = await Promise.all( 68 | toolCalls.map(async (tc) => { 69 | return await upsertMemoryTool.invoke(tc); 70 | }), 71 | ); 72 | 73 | return { messages: savedMemories }; 74 | } 75 | 76 | function routeMessage( 77 | state: typeof GraphAnnotation.State, 78 | ): "store_memory" | typeof END { 79 | const lastMessage = state.messages[state.messages.length - 1] as AIMessage; 80 | if (lastMessage.tool_calls?.length) { 81 | return "store_memory"; 82 | } 83 | return END; 84 | } 85 | 86 | // Create the graph + all nodes 87 | export const builder = new StateGraph( 88 | { 89 | stateSchema: GraphAnnotation, 90 | }, 91 | ConfigurationAnnotation, 92 | ) 93 | .addNode("call_model", callModel) 94 | .addNode("store_memory", storeMemory) 95 | .addEdge(START, "call_model") 96 | .addConditionalEdges("call_model", routeMessage, { 97 | store_memory: "store_memory", 98 | [END]: END, 99 | }) 100 | .addEdge("store_memory", "call_model"); 101 | 102 | export const graph = builder.compile(); 103 | graph.name = "MemoryAgent"; 104 | -------------------------------------------------------------------------------- /templates/memory-agent/prompts.ts: -------------------------------------------------------------------------------- 1 | // Define default prompts 2 | 3 | export const SYSTEM_PROMPT = `You are a helpful and friendly chatbot. Get to know the user! \ 4 | Ask questions! Be spontaneous! 5 | {user_info} 6 | 7 | System Time: {time}`; 8 | -------------------------------------------------------------------------------- /templates/memory-agent/state.ts: -------------------------------------------------------------------------------- 1 | import { BaseMessage } from "@langchain/core/messages"; 2 | import { 3 | Annotation, 4 | Messages, 5 | messagesStateReducer, 6 | } from "@langchain/langgraph"; 7 | 8 | /** 9 | * Main graph state. 10 | */ 11 | export const GraphAnnotation = Annotation.Root({ 12 | /** 13 | * The messages in the conversation. 14 | */ 15 | messages: Annotation({ 16 | reducer: messagesStateReducer, 17 | default: () => [], 18 | }), 19 | }); 20 | -------------------------------------------------------------------------------- /templates/memory-agent/static/memories.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langchain-ai/create-agent-chat-app/38c4631e6eaa806dc78b1636688ae1520409d93a/templates/memory-agent/static/memories.png -------------------------------------------------------------------------------- /templates/memory-agent/static/memory_graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langchain-ai/create-agent-chat-app/38c4631e6eaa806dc78b1636688ae1520409d93a/templates/memory-agent/static/memory_graph.png -------------------------------------------------------------------------------- /templates/memory-agent/tests/integration/graph.int.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "@jest/globals"; 2 | import { MemorySaver, InMemoryStore } from "@langchain/langgraph"; 3 | import { builder } from "../../graph.js"; 4 | 5 | describe("Memory Graph", () => { 6 | const conversations = [ 7 | ["My name is Alice and I love pizza. Remember this."], 8 | [ 9 | "Hi, I'm Bob and I enjoy playing tennis. Remember this.", 10 | "Yes, I also have a pet dog named Max.", 11 | "Max is a golden retriever and he's 5 years old. Please remember this too.", 12 | ], 13 | [ 14 | "Hello, I'm Charlie. I work as a software engineer and I'm passionate about AI. Remember this.", 15 | "I specialize in machine learning algorithms and I'm currently working on a project involving natural language processing.", 16 | "My main goal is to improve sentiment analysis accuracy in multi-lingual texts. It's challenging but exciting.", 17 | "We've made some progress using transformer models, but we're still working on handling context and idioms across languages.", 18 | "Chinese and English have been the most challenging pair so far due to their vast differences in structure and cultural contexts.", 19 | ], 20 | ]; 21 | 22 | it.each( 23 | conversations.map((conversation, index) => [ 24 | ["short", "medium", "long"][index], 25 | conversation, 26 | ]), 27 | )( 28 | "should store memories for %s conversation", 29 | async (_, conversation) => { 30 | const memStore = new InMemoryStore(); 31 | const graph = builder.compile({ 32 | store: memStore, 33 | checkpointer: new MemorySaver(), 34 | }); 35 | const userId = "test-user"; 36 | for (const content of conversation) { 37 | await graph.invoke( 38 | { 39 | messages: [ 40 | { role: "user", content: [{ type: "text", text: content }] }, 41 | ], 42 | }, 43 | { 44 | configurable: { 45 | userId, 46 | thread_id: "thread", 47 | model: "gpt-4o-mini", 48 | systemPrompt: "You are a helpful assistant.", 49 | }, 50 | }, 51 | ); 52 | } 53 | 54 | const namespace = ["memories", userId]; 55 | const memories = await memStore.search(namespace); 56 | expect(memories.length).toBeGreaterThan(0); 57 | 58 | const badNamespace = ["memories", "wrong-user"]; 59 | const badMemories = await memStore.search(badNamespace); 60 | expect(badMemories.length).toBe(0); 61 | }, 62 | 30000, 63 | ); 64 | }); 65 | -------------------------------------------------------------------------------- /templates/memory-agent/tests/unit/configuration.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "@jest/globals"; 2 | import { ensureConfiguration } from "../../configuration.js"; 3 | 4 | describe("Configuration", () => { 5 | it("should initialize configuration from an empty object", () => { 6 | const emptyConfig = {}; 7 | const result = ensureConfiguration(emptyConfig); 8 | expect(result).toBeDefined(); 9 | expect(typeof result).toBe("object"); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /templates/memory-agent/tests/unit/graph.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from "@jest/globals"; 2 | import { graph } from "../../graph.js"; 3 | 4 | describe("Memory Graph", () => { 5 | it("should initialize and compile the graph", () => { 6 | expect(graph).toBeDefined(); 7 | expect(graph.name).toBe("MemoryAgent"); 8 | }); 9 | 10 | // TODO: Add more test cases for individual nodes, routing logic, tool integration, and output validation 11 | }); 12 | -------------------------------------------------------------------------------- /templates/memory-agent/tools.ts: -------------------------------------------------------------------------------- 1 | import { LangGraphRunnableConfig } from "@langchain/langgraph"; 2 | import { ensureConfiguration } from "./configuration.js"; 3 | import { v4 as uuidv4 } from "uuid"; 4 | import { tool } from "@langchain/core/tools"; 5 | import { z } from "zod"; 6 | import { getStoreFromConfigOrThrow } from "./utils.js"; 7 | 8 | /** 9 | * Initialize tools within a function so that they have access to the current 10 | * state and config at runtime. 11 | */ 12 | export function initializeTools(config?: LangGraphRunnableConfig) { 13 | /** 14 | * Upsert a memory in the database. 15 | * @param content The main content of the memory. 16 | * @param context Additional context for the memory. 17 | * @param memoryId Optional ID to overwrite an existing memory. 18 | * @returns A string confirming the memory storage. 19 | */ 20 | async function upsertMemory(opts: { 21 | content: string; 22 | context: string; 23 | memoryId?: string; 24 | }): Promise { 25 | const { content, context, memoryId } = opts; 26 | if (!config || !config.store) { 27 | throw new Error("Config or store not provided"); 28 | } 29 | 30 | const configurable = ensureConfiguration(config); 31 | const memId = memoryId || uuidv4(); 32 | const store = getStoreFromConfigOrThrow(config); 33 | 34 | await store.put(["memories", configurable.userId], memId, { 35 | content, 36 | context, 37 | }); 38 | 39 | return `Stored memory ${memId}`; 40 | } 41 | 42 | const upsertMemoryTool = tool(upsertMemory, { 43 | name: "upsertMemory", 44 | description: 45 | "Upsert a memory in the database. If a memory conflicts with an existing one, \ 46 | update the existing one by passing in the memory_id instead of creating a duplicate. \ 47 | If the user corrects a memory, update it. Can call multiple times in parallel \ 48 | if you need to store or update multiple memories.", 49 | schema: z.object({ 50 | content: z.string().describe( 51 | "The main content of the memory. For example: \ 52 | 'User expressed interest in learning about French.'", 53 | ), 54 | context: z.string().describe( 55 | "Additional context for the memory. For example: \ 56 | 'This was mentioned while discussing career options in Europe.'", 57 | ), 58 | memoryId: z 59 | .string() 60 | .optional() 61 | .describe( 62 | "The memory ID to overwrite. Only provide if updating an existing memory.", 63 | ), 64 | }), 65 | }); 66 | 67 | return [upsertMemoryTool]; 68 | } 69 | -------------------------------------------------------------------------------- /templates/memory-agent/utils.ts: -------------------------------------------------------------------------------- 1 | import { BaseStore, LangGraphRunnableConfig } from "@langchain/langgraph"; 2 | /** 3 | * Get the store from the configuration or throw an error. 4 | */ 5 | export function getStoreFromConfigOrThrow( 6 | config: LangGraphRunnableConfig, 7 | ): BaseStore { 8 | if (!config.store) { 9 | throw new Error("Store not found in configuration"); 10 | } 11 | 12 | return config.store; 13 | } 14 | 15 | /** 16 | * Split the fully specified model name into model and provider. 17 | */ 18 | export function splitModelAndProvider(fullySpecifiedName: string): { 19 | model: string; 20 | provider?: string; 21 | } { 22 | let provider: string | undefined; 23 | let model: string; 24 | 25 | if (fullySpecifiedName.includes("/")) { 26 | [provider, model] = fullySpecifiedName.split("/", 2); 27 | } else { 28 | model = fullySpecifiedName; 29 | } 30 | 31 | return { model, provider }; 32 | } 33 | -------------------------------------------------------------------------------- /templates/monorepo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | **/node_modules 6 | /.pnp 7 | .pnp.js 8 | .yarn/install-state.gz 9 | .yarn/cache 10 | 11 | # testing 12 | /coverage 13 | 14 | # next.js 15 | /.next/ 16 | /out/ 17 | 18 | # production 19 | /build 20 | /dist 21 | **/dist 22 | .turbo/ 23 | 24 | # misc 25 | .DS_Store 26 | *.pem 27 | 28 | # debug 29 | npm-debug.log* 30 | yarn-debug.log* 31 | yarn-error.log* 32 | 33 | # local env files 34 | .env*.local 35 | .env 36 | 37 | # vercel 38 | .vercel 39 | 40 | # typescript 41 | *.tsbuildinfo 42 | next-env.d.ts 43 | 44 | credentials.json 45 | 46 | # LangGraph API 47 | .langgraph_api 48 | -------------------------------------------------------------------------------- /templates/monorepo/README.md: -------------------------------------------------------------------------------- 1 | # Agent App Monorepo 2 | 3 | This monorepo contains a Chat UI, and up to four pre-built LangGraph agents. 4 | 5 | ## Setup 6 | 7 | First, install dependencies, if you didn't select the auto-install option when creating the app: 8 | 9 | ```bash 10 | yarn install 11 | # or 12 | pnpm install 13 | # or 14 | npm install 15 | ``` 16 | 17 | Then, set the necessary environment variables: 18 | 19 | ```bash 20 | cp .env.example .env 21 | ``` 22 | 23 | When running the Chat UI, it will prompt you to enter your deployment URL, assistant ID, and LangSmith API key. If you want to hardcode these values, and bypass the initial setup form, you can set the following environment variables inside `apps/web/.env`: 24 | 25 | If using Vite: 26 | 27 | ```bash 28 | VITE_API_URL=http://localhost:2024 29 | VITE_ASSISTANT_ID=agent 30 | ``` 31 | 32 | If using Next.js: 33 | 34 | ```bash 35 | NEXT_PUBLIC_API_URL=http://localhost:2024 36 | NEXT_PUBLIC_ASSISTANT_ID=agent 37 | ``` 38 | 39 | > [!TIP] 40 | > If you want to connect to a production LangGraph server, read the [Going to Production](apps/web/README.md#going-to-production) section in the Chat UI readme. 41 | 42 | Once you have all the necessary environment variables set, you can run both the Chat UI, and agent server in dev mode: 43 | 44 | ```bash 45 | yarn dev 46 | # or 47 | pnpm dev 48 | # or 49 | npm run dev 50 | ``` 51 | 52 | For more information on the agents, and Chat UI, read their respective READMEs: 53 | 54 | - [Chat UI](apps/web/README.md) 55 | - [Agents](apps/agents/README.md) 56 | -------------------------------------------------------------------------------- /templates/monorepo/apps/agents/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | .yarn/cache 9 | 10 | # testing 11 | /coverage 12 | 13 | # next.js 14 | /.next/ 15 | /out/ 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # local env files 30 | .env*.local 31 | .env 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | next-env.d.ts 39 | 40 | credentials.json 41 | 42 | # LangGraph API 43 | .langgraph_api 44 | -------------------------------------------------------------------------------- /templates/monorepo/apps/agents/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "printWidth": 80, 4 | "tabWidth": 2, 5 | "useTabs": false, 6 | "semi": true, 7 | "singleQuote": false, 8 | "quoteProps": "as-needed", 9 | "jsxSingleQuote": false, 10 | "trailingComma": "es5", 11 | "bracketSpacing": true, 12 | "arrowParens": "always", 13 | "requirePragma": false, 14 | "insertPragma": false, 15 | "proseWrap": "preserve", 16 | "htmlWhitespaceSensitivity": "css", 17 | "vueIndentScriptAndStyle": false, 18 | "endOfLine": "lf" 19 | } 20 | -------------------------------------------------------------------------------- /templates/monorepo/apps/agents/README.md: -------------------------------------------------------------------------------- 1 | # LangGraph Agents 2 | 3 | This package contains the agents you selected when configuring the Agent Chat App. Each individual agent has its own `README.md` file with instructions specific to that agent. For information on each agent, read their respective READMEs. 4 | -------------------------------------------------------------------------------- /templates/monorepo/apps/agents/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import tseslint from "@typescript-eslint/eslint-plugin"; 3 | import tsparser from "@typescript-eslint/parser"; 4 | import importPlugin from "eslint-plugin-import"; 5 | import noInstanceofPlugin from "eslint-plugin-no-instanceof"; 6 | import prettierConfig from "eslint-config-prettier"; 7 | import globals from "globals"; 8 | 9 | export default [ 10 | js.configs.recommended, 11 | prettierConfig, 12 | { 13 | files: ["**/*.ts", "**/*.tsx"], 14 | plugins: { 15 | "@typescript-eslint": tseslint, 16 | import: importPlugin, 17 | "no-instanceof": noInstanceofPlugin, 18 | }, 19 | languageOptions: { 20 | parser: tsparser, 21 | parserOptions: { 22 | ecmaVersion: 2021, 23 | sourceType: "module", 24 | project: "./tsconfig.json", 25 | tsconfigRootDir: ".", 26 | }, 27 | globals: { 28 | ...globals.node, 29 | ...globals.browser, 30 | }, 31 | }, 32 | ignores: [ 33 | "node_modules/**", 34 | "dist/**", 35 | "dist-cjs/**", 36 | "eslint.config.js", 37 | "scripts/**", 38 | "*.d.ts", 39 | ], 40 | rules: { 41 | "@typescript-eslint/explicit-module-boundary-types": 0, 42 | "@typescript-eslint/no-empty-function": 0, 43 | "@typescript-eslint/no-shadow": 0, 44 | "@typescript-eslint/no-empty-interface": 0, 45 | "no-unused-vars": 0, 46 | "@typescript-eslint/no-use-before-define": ["error", "nofunc"], 47 | "@typescript-eslint/no-unused-vars": [ 48 | "warn", 49 | { 50 | args: "none", 51 | varsIgnorePattern: "^_", 52 | argsIgnorePattern: "^_", 53 | caughtErrorsIgnorePattern: "^_", 54 | destructuredArrayIgnorePattern: "^_", 55 | ignoreRestSiblings: true, 56 | }, 57 | ], 58 | "@typescript-eslint/no-floating-promises": "error", 59 | "@typescript-eslint/no-misused-promises": "error", 60 | "@typescript-eslint/no-explicit-any": 0, 61 | camelcase: 0, 62 | "class-methods-use-this": 0, 63 | "import/extensions": [2, "ignorePackages"], 64 | "import/no-extraneous-dependencies": [ 65 | "error", 66 | { devDependencies: ["**/*.test.ts"] }, 67 | ], 68 | "import/no-unresolved": 0, 69 | "import/prefer-default-export": 0, 70 | "keyword-spacing": "error", 71 | "max-classes-per-file": 0, 72 | "max-len": 0, 73 | "no-await-in-loop": 0, 74 | "no-bitwise": 0, 75 | "no-console": 0, 76 | "no-restricted-syntax": 0, 77 | "no-shadow": 0, 78 | "no-continue": 0, 79 | "no-underscore-dangle": 0, 80 | "no-use-before-define": 0, 81 | "no-useless-constructor": 0, 82 | "no-return-await": 0, 83 | "consistent-return": 0, 84 | "no-else-return": 0, 85 | "new-cap": ["error", { properties: false, capIsNew: false }], 86 | }, 87 | }, 88 | ]; 89 | -------------------------------------------------------------------------------- /templates/monorepo/apps/agents/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "agents", 3 | "author": "Your Name", 4 | "version": "0.0.1", 5 | "private": true, 6 | "type": "module", 7 | "scripts": { 8 | "dev": "npx @langchain/langgraph-cli dev --port 2024 --config ../../langgraph.json", 9 | "build": "turbo build:internal --filter=agents", 10 | "build:internal": "{PACKAGE_MANAGER} run clean && tsc", 11 | "clean": "rm -rf ./dist .turbo || true", 12 | "format": "prettier --config .prettierrc --write \"src\"", 13 | "lint": "eslint src", 14 | "lint:fix": "eslint src --fix" 15 | }, 16 | "dependencies": { 17 | "@langchain/core": "^0.3.42", 18 | "@langchain/langgraph": "^0.2.55", 19 | "langchain": "^0.3.19", 20 | "dotenv": "^16.4.5", 21 | "uuid": "^10.0.0", 22 | "zod": "^3.23.8" 23 | }, 24 | "devDependencies": { 25 | "@typescript-eslint/eslint-plugin": "^8.26.1", 26 | "@typescript-eslint/parser": "^8.26.1", 27 | "eslint": "^9.19.0", 28 | "eslint-config-prettier": "^10.1.1", 29 | "eslint-plugin-import": "^2.31.0", 30 | "@jest/globals": "^29.7.0", 31 | "globals": "^15.14.0", 32 | "eslint-plugin-no-instanceof": "^1.0.1", 33 | "eslint-plugin-prettier": "^5.2.3", 34 | "@eslint/eslintrc": "^3.3.0", 35 | "@eslint/js": "^9.22.0", 36 | "@types/node": "^20", 37 | "@types/uuid": "^10.0.0", 38 | "prettier": "^3.3.3", 39 | "tsx": "^4.19.1", 40 | "turbo": "latest", 41 | "typescript": "^5" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /templates/monorepo/apps/agents/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/recommended", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "rootDir": "./src", 6 | "baseUrl": ".", 7 | "target": "ES2021", 8 | "lib": ["ES2021", "ES2022.Object", "DOM", "es2023"], 9 | "module": "NodeNext", 10 | "moduleResolution": "NodeNext", 11 | "esModuleInterop": true, 12 | "declaration": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "useDefineForClassFields": true, 18 | "strictPropertyInitialization": false, 19 | "allowJs": true, 20 | "strict": true 21 | }, 22 | "include": ["src/"], 23 | "exclude": ["node_modules/", "dist"] 24 | } 25 | -------------------------------------------------------------------------------- /templates/monorepo/apps/agents/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["//"], 3 | "tasks": { 4 | "build": { 5 | "outputs": ["**/dist/**"] 6 | }, 7 | "build:internal": { 8 | "dependsOn": ["^build:internal"] 9 | }, 10 | "dev": { 11 | "dependsOn": ["^dev"] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /templates/monorepo/langgraph.json: -------------------------------------------------------------------------------- 1 | { 2 | "node_version": "20", 3 | "dependencies": ["."], 4 | "graphs": {}, 5 | "env": ".env" 6 | } 7 | -------------------------------------------------------------------------------- /templates/monorepo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "agent-app", 3 | "author": "Your Name", 4 | "private": true, 5 | "workspaces": [ 6 | "apps/*" 7 | ], 8 | "scripts": { 9 | "dev": "concurrently \"turbo dev --filter=web\" \"turbo dev --filter=agents\"", 10 | "build": "turbo build", 11 | "turbo:command": "turbo", 12 | "format": "turbo format", 13 | "lint": "turbo lint", 14 | "lint:fix": "turbo lint:fix" 15 | }, 16 | "devDependencies": { 17 | "turbo": "latest", 18 | "tsx": "^4.19.1", 19 | "typescript": "^5", 20 | "eslint": "^9.19.0", 21 | "concurrently": "^9.1.2", 22 | "@typescript-eslint/eslint-plugin": "^8.26.1", 23 | "@eslint/eslintrc": "^3.3.0", 24 | "@typescript-eslint/parser": "^8.26.1", 25 | "@tsconfig/recommended": "^1.0.8", 26 | "eslint-config-prettier": "^10.1.1", 27 | "eslint-plugin-import": "^2.31.0", 28 | "eslint-plugin-no-instanceof": "^1.0.1", 29 | "eslint-plugin-prettier": "^5.2.3", 30 | "prettier": "^3.3.3" 31 | }, 32 | "resolutions": { 33 | "@langchain/core": "^0.3.42" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /templates/monorepo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ] 20 | }, 21 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 22 | "exclude": ["node_modules"] 23 | } 24 | -------------------------------------------------------------------------------- /templates/monorepo/turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "globalDependencies": ["**/.env"], 4 | "tasks": { 5 | "build": { 6 | "dependsOn": ["^build"], 7 | "outputs": ["dist/**", ".next/**", "!.next/cache/**"] 8 | }, 9 | "lint": { 10 | "dependsOn": ["^lint"] 11 | }, 12 | "lint:fix": { 13 | "dependsOn": ["^lint:fix"] 14 | }, 15 | "format": { 16 | "dependsOn": ["^format"] 17 | }, 18 | "dev": { 19 | "dependsOn": ["^dev"] 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /templates/nextjs/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | .git 4 | .env -------------------------------------------------------------------------------- /templates/nextjs/.env.example: -------------------------------------------------------------------------------- 1 | # LangGraph Configuration 2 | NEXT_PUBLIC_API_URL=http://localhost:2024 3 | NEXT_PUBLIC_ASSISTANT_ID=agent 4 | # Do NOT prefix this with "NEXT_PUBLIC_" as we do not want this exposed in the client. 5 | LANGSMITH_API_KEY= 6 | 7 | # Production LangGraph Configuration (quickstart) - Uncomment to use 8 | # NEXT_PUBLIC_ASSISTANT_ID="agent" 9 | # This should be the deployment URL of your LangGraph server 10 | # LANGGRAPH_API_URL="https://my-agent.default.us.langgraph.app" 11 | # This should be the URL of your website + "/api". This is how you connect to the API proxy 12 | # NEXT_PUBLIC_API_URL="https://my-website.com/api" 13 | # LANGSMITH_API_KEY="lsv2_..." 14 | -------------------------------------------------------------------------------- /templates/nextjs/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | # LangGraph API 27 | .langgraph_api 28 | .env 29 | .next/ 30 | next-env.d.ts -------------------------------------------------------------------------------- /templates/nextjs/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false 4 | } 5 | -------------------------------------------------------------------------------- /templates/nextjs/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/index.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /templates/nextjs/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import globals from "globals"; 3 | import reactHooks from "eslint-plugin-react-hooks"; 4 | import reactRefresh from "eslint-plugin-react-refresh"; 5 | import tseslint from "typescript-eslint"; 6 | 7 | export default tseslint.config( 8 | { ignores: ["dist"] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ["**/*.{ts,tsx}"], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | "react-hooks": reactHooks, 18 | "react-refresh": reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | "@typescript-eslint/no-explicit-any": 0, 23 | "@typescript-eslint/no-unused-vars": [ 24 | "warn", 25 | { args: "none", argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, 26 | ], 27 | "react-refresh/only-export-components": [ 28 | "warn", 29 | { allowConstantExport: true }, 30 | ], 31 | }, 32 | }, 33 | ); 34 | -------------------------------------------------------------------------------- /templates/nextjs/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /templates/nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "next dev", 8 | "build": "turbo build:internal --filter=web", 9 | "build:internal": "next build", 10 | "start": "next start", 11 | "lint": "next lint", 12 | "lint:fix": "next lint --fix", 13 | "format": "prettier --write .", 14 | "format:check": "prettier --check ." 15 | }, 16 | "dependencies": { 17 | "@langchain/core": "^0.3.42", 18 | "@langchain/langgraph": "^0.2.55", 19 | "@langchain/langgraph-api": "^0.0.16", 20 | "@langchain/langgraph-cli": "^0.0.16", 21 | "@langchain/langgraph-sdk": "^0.0.57", 22 | "@radix-ui/react-avatar": "^1.1.3", 23 | "@radix-ui/react-dialog": "^1.1.6", 24 | "@radix-ui/react-label": "^2.1.2", 25 | "@radix-ui/react-separator": "^1.1.2", 26 | "@radix-ui/react-slot": "^1.1.2", 27 | "@radix-ui/react-switch": "^1.1.3", 28 | "@radix-ui/react-tooltip": "^1.1.8", 29 | "class-variance-authority": "^0.7.1", 30 | "clsx": "^2.1.1", 31 | "date-fns": "^4.1.0", 32 | "esbuild": "^0.25.0", 33 | "esbuild-plugin-tailwindcss": "^2.0.1", 34 | "framer-motion": "^12.4.9", 35 | "katex": "^0.16.21", 36 | "langgraph-nextjs-api-passthrough": "^0.0.4", 37 | "lodash": "^4.17.21", 38 | "lucide-react": "^0.476.0", 39 | "next-themes": "^0.4.4", 40 | "prettier": "^3.5.2", 41 | "react": "^19.0.0", 42 | "react-dom": "^19.0.0", 43 | "react-markdown": "^10.0.1", 44 | "nuqs": "^2.4.1", 45 | "react-syntax-highlighter": "^15.5.0", 46 | "recharts": "^2.15.1", 47 | "rehype-katex": "^7.0.1", 48 | "remark-gfm": "^4.0.1", 49 | "remark-math": "^6.0.0", 50 | "sonner": "^2.0.1", 51 | "tailwind-merge": "^3.0.2", 52 | "tailwindcss-animate": "^1.0.7", 53 | "use-stick-to-bottom": "^1.0.46", 54 | "uuid": "^11.0.5", 55 | "zod": "^3.24.2" 56 | }, 57 | "devDependencies": { 58 | "@eslint/js": "^9.19.0", 59 | "@tailwindcss/postcss": "^4.0.13", 60 | "@types/lodash": "^4.17.16", 61 | "@types/node": "^22.13.5", 62 | "@types/react": "^19.0.8", 63 | "@types/react-dom": "^19.0.3", 64 | "@types/react-syntax-highlighter": "^15.5.13", 65 | "autoprefixer": "^10.4.20", 66 | "dotenv": "^16.4.7", 67 | "turbo": "latest", 68 | "eslint": "^9.19.0", 69 | "eslint-config-next": "15.2.2", 70 | "eslint-plugin-react-hooks": "^5.0.0", 71 | "eslint-plugin-react-refresh": "^0.4.18", 72 | "globals": "^15.14.0", 73 | "next": "^15.2.3", 74 | "postcss": "^8.5.3", 75 | "tailwind-scrollbar": "^4.0.1", 76 | "tailwindcss": "^4.0.13", 77 | "typescript": "~5.7.2", 78 | "typescript-eslint": "^8.22.0" 79 | }, 80 | "overrides": { 81 | "react-is": "^19.0.0-rc-69d4b800-20241021" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /templates/nextjs/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | "@tailwindcss/postcss": {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /templates/nextjs/src/app/api/[..._path]/route.ts: -------------------------------------------------------------------------------- 1 | import { initApiPassthrough } from "langgraph-nextjs-api-passthrough"; 2 | 3 | // This file acts as a proxy for requests to your LangGraph server. 4 | // Read the [Going to Production](https://github.com/langchain-ai/agent-chat-ui?tab=readme-ov-file#going-to-production) section for more information. 5 | 6 | export const { GET, POST, PUT, PATCH, DELETE, OPTIONS, runtime } = 7 | initApiPassthrough({ 8 | apiUrl: process.env.LANGGRAPH_API_URL ?? "remove-me", // default, if not defined it will attempt to read process.env.LANGGRAPH_API_URL 9 | apiKey: process.env.LANGSMITH_API_KEY ?? "remove-me", // default, if not defined it will attempt to read process.env.LANGSMITH_API_KEY 10 | runtime: "edge", // default 11 | }); 12 | -------------------------------------------------------------------------------- /templates/nextjs/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langchain-ai/create-agent-chat-app/38c4631e6eaa806dc78b1636688ae1520409d93a/templates/nextjs/src/app/favicon.ico -------------------------------------------------------------------------------- /templates/nextjs/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import "./globals.css"; 3 | import { Inter } from "next/font/google"; 4 | import React from "react"; 5 | import { NuqsAdapter } from "nuqs/adapters/next/app"; 6 | 7 | const inter = Inter({ 8 | subsets: ["latin"], 9 | preload: true, 10 | display: "swap", 11 | }); 12 | 13 | export const metadata: Metadata = { 14 | title: "Agent Inbox", 15 | description: "Agent Inbox UX by LangChain", 16 | }; 17 | 18 | export default function RootLayout({ 19 | children, 20 | }: Readonly<{ 21 | children: React.ReactNode; 22 | }>) { 23 | return ( 24 | 25 | 26 | {children} 27 | 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /templates/nextjs/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Thread } from "@/components/thread"; 4 | import { StreamProvider } from "@/providers/Stream"; 5 | import { ThreadProvider } from "@/providers/Thread"; 6 | import { Toaster } from "@/components/ui/sonner"; 7 | import React from "react"; 8 | 9 | export default function DemoPage(): React.ReactNode { 10 | return ( 11 | Loading (layout)...}> 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /templates/nextjs/src/components/icons/github.tsx: -------------------------------------------------------------------------------- 1 | export const GitHubSVG = ({ width = "100%", height = "100%" }) => ( 2 | 9 | GitHub 10 | 11 | 12 | ); 13 | -------------------------------------------------------------------------------- /templates/nextjs/src/components/thread/agent-inbox/components/thread-id.tsx: -------------------------------------------------------------------------------- 1 | import { Copy, CopyCheck } from "lucide-react"; 2 | import { 3 | Tooltip, 4 | TooltipContent, 5 | TooltipProvider, 6 | TooltipTrigger, 7 | } from "@/components/ui/tooltip"; 8 | import React from "react"; 9 | import { motion, AnimatePresence } from "framer-motion"; 10 | import { TooltipIconButton } from "../../tooltip-icon-button"; 11 | 12 | export function ThreadIdTooltip({ threadId }: { threadId: string }) { 13 | const firstThreeChars = threadId.slice(0, 3); 14 | const lastThreeChars = threadId.slice(-3); 15 | 16 | return ( 17 | 18 | 19 | 20 |

21 | {firstThreeChars}...{lastThreeChars} 22 |

23 |
24 | 25 | 26 | 27 |
28 |
29 | ); 30 | } 31 | 32 | export function ThreadIdCopyable({ 33 | threadId, 34 | showUUID = false, 35 | }: { 36 | threadId: string; 37 | showUUID?: boolean; 38 | }) { 39 | const [copied, setCopied] = React.useState(false); 40 | 41 | const handleCopy = (e: React.MouseEvent) => { 42 | e.stopPropagation(); 43 | navigator.clipboard.writeText(threadId); 44 | setCopied(true); 45 | setTimeout(() => setCopied(false), 2000); 46 | }; 47 | 48 | return ( 49 | handleCopy(e)} 51 | variant="ghost" 52 | tooltip="Copy thread ID" 53 | className="flex flex-grow-0 gap-1 items-center p-1 rounded-md border-[1px] cursor-pointer hover:bg-gray-50/90 border-gray-200 w-fit" 54 | > 55 |

{showUUID ? threadId : "ID"}

56 | 57 | {copied ? ( 58 | 65 | 66 | 67 | ) : ( 68 | 75 | 76 | 77 | )} 78 | 79 |
80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /templates/nextjs/src/components/thread/agent-inbox/components/tool-call-table.tsx: -------------------------------------------------------------------------------- 1 | import { ToolCall } from "@langchain/core/messages/tool"; 2 | import { unknownToPrettyDate } from "../utils"; 3 | 4 | export function ToolCallTable({ toolCall }: { toolCall: ToolCall }) { 5 | return ( 6 |
7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | {Object.entries(toolCall.args).map(([key, value]) => { 17 | let valueStr = ""; 18 | if (["string", "number"].includes(typeof value)) { 19 | valueStr = value.toString(); 20 | } 21 | 22 | const date = unknownToPrettyDate(value); 23 | if (date) { 24 | valueStr = date; 25 | } 26 | 27 | try { 28 | valueStr = valueStr || JSON.stringify(value, null); 29 | } catch (_) { 30 | // failed to stringify, just assign an empty string 31 | valueStr = ""; 32 | } 33 | 34 | return ( 35 | 36 | 37 | 38 | 39 | ); 40 | })} 41 | 42 |
11 | {toolCall.name} 12 |
{key}{valueStr}
43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /templates/nextjs/src/components/thread/agent-inbox/index.tsx: -------------------------------------------------------------------------------- 1 | import { StateView } from "./components/state-view"; 2 | import { ThreadActionsView } from "./components/thread-actions-view"; 3 | import { useState } from "react"; 4 | import { HumanInterrupt } from "@langchain/langgraph/prebuilt"; 5 | import { useStreamContext } from "@/providers/Stream"; 6 | 7 | interface ThreadViewProps { 8 | interrupt: HumanInterrupt | HumanInterrupt[]; 9 | } 10 | 11 | export function ThreadView({ interrupt }: ThreadViewProps) { 12 | const interruptObj = Array.isArray(interrupt) ? interrupt[0] : interrupt; 13 | const thread = useStreamContext(); 14 | const [showDescription, setShowDescription] = useState(false); 15 | const [showState, setShowState] = useState(false); 16 | const showSidePanel = showDescription || showState; 17 | 18 | const handleShowSidePanel = ( 19 | showState: boolean, 20 | showDescription: boolean, 21 | ) => { 22 | if (showState && showDescription) { 23 | console.error("Cannot show both state and description"); 24 | return; 25 | } 26 | if (showState) { 27 | setShowDescription(false); 28 | setShowState(true); 29 | } else if (showDescription) { 30 | setShowState(false); 31 | setShowDescription(true); 32 | } else { 33 | setShowState(false); 34 | setShowDescription(false); 35 | } 36 | }; 37 | 38 | return ( 39 |
40 | {showSidePanel ? ( 41 | 47 | ) : ( 48 | 54 | )} 55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /templates/nextjs/src/components/thread/agent-inbox/types.ts: -------------------------------------------------------------------------------- 1 | import { BaseMessage } from "@langchain/core/messages"; 2 | import { Thread, ThreadStatus } from "@langchain/langgraph-sdk"; 3 | import { HumanInterrupt, HumanResponse } from "@langchain/langgraph/prebuilt"; 4 | 5 | export type HumanResponseWithEdits = HumanResponse & 6 | ( 7 | | { acceptAllowed?: false; editsMade?: never } 8 | | { acceptAllowed?: true; editsMade?: boolean } 9 | ); 10 | 11 | export type Email = { 12 | id: string; 13 | thread_id: string; 14 | from_email: string; 15 | to_email: string; 16 | subject: string; 17 | page_content: string; 18 | send_time: string | undefined; 19 | read?: boolean; 20 | status?: "in-queue" | "processing" | "hitl" | "done"; 21 | }; 22 | 23 | export interface ThreadValues { 24 | email: Email; 25 | messages: BaseMessage[]; 26 | triage: { 27 | logic: string; 28 | response: string; 29 | }; 30 | } 31 | 32 | export type ThreadData< 33 | ThreadValues extends Record = Record, 34 | > = { 35 | thread: Thread; 36 | } & ( 37 | | { 38 | status: "interrupted"; 39 | interrupts: HumanInterrupt[] | undefined; 40 | } 41 | | { 42 | status: "idle" | "busy" | "error"; 43 | interrupts?: never; 44 | } 45 | ); 46 | 47 | export type ThreadStatusWithAll = ThreadStatus | "all"; 48 | 49 | export type SubmitType = "accept" | "response" | "edit"; 50 | 51 | export interface AgentInbox { 52 | /** 53 | * A unique identifier for the inbox. 54 | */ 55 | id: string; 56 | /** 57 | * The ID of the graph. 58 | */ 59 | graphId: string; 60 | /** 61 | * The URL of the deployment. Either a localhost URL, or a deployment URL. 62 | */ 63 | deploymentUrl: string; 64 | /** 65 | * Optional name for the inbox, used in the UI to label the inbox. 66 | */ 67 | name?: string; 68 | /** 69 | * Whether or not the inbox is selected. 70 | */ 71 | selected: boolean; 72 | } 73 | -------------------------------------------------------------------------------- /templates/nextjs/src/components/thread/history/index.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import { useThreads } from "@/providers/Thread"; 3 | import { Thread } from "@langchain/langgraph-sdk"; 4 | import { useEffect } from "react"; 5 | 6 | import { getContentString } from "../utils"; 7 | import { useQueryState, parseAsBoolean } from "nuqs"; 8 | import { 9 | Sheet, 10 | SheetContent, 11 | SheetHeader, 12 | SheetTitle, 13 | } from "@/components/ui/sheet"; 14 | import { Skeleton } from "@/components/ui/skeleton"; 15 | import { PanelRightOpen, PanelRightClose } from "lucide-react"; 16 | import { useMediaQuery } from "@/hooks/useMediaQuery"; 17 | 18 | function ThreadList({ 19 | threads, 20 | onThreadClick, 21 | }: { 22 | threads: Thread[]; 23 | onThreadClick?: (threadId: string) => void; 24 | }) { 25 | const [threadId, setThreadId] = useQueryState("threadId"); 26 | 27 | return ( 28 |
29 | {threads.map((t) => { 30 | let itemText = t.thread_id; 31 | if ( 32 | typeof t.values === "object" && 33 | t.values && 34 | "messages" in t.values && 35 | Array.isArray(t.values.messages) && 36 | t.values.messages?.length > 0 37 | ) { 38 | const firstMessage = t.values.messages[0]; 39 | itemText = getContentString(firstMessage.content); 40 | } 41 | return ( 42 |
43 | 55 |
56 | ); 57 | })} 58 |
59 | ); 60 | } 61 | 62 | function ThreadHistoryLoading() { 63 | return ( 64 |
65 | {Array.from({ length: 30 }).map((_, i) => ( 66 | 67 | ))} 68 |
69 | ); 70 | } 71 | 72 | export default function ThreadHistory() { 73 | const isLargeScreen = useMediaQuery("(min-width: 1024px)"); 74 | const [chatHistoryOpen, setChatHistoryOpen] = useQueryState( 75 | "chatHistoryOpen", 76 | parseAsBoolean.withDefault(false), 77 | ); 78 | 79 | const { getThreads, threads, setThreads, threadsLoading, setThreadsLoading } = 80 | useThreads(); 81 | 82 | useEffect(() => { 83 | if (typeof window === "undefined") return; 84 | setThreadsLoading(true); 85 | getThreads() 86 | .then(setThreads) 87 | .catch(console.error) 88 | .finally(() => setThreadsLoading(false)); 89 | }, []); 90 | 91 | return ( 92 | <> 93 |
94 |
95 | 106 |

107 | Thread History 108 |

109 |
110 | {threadsLoading ? ( 111 | 112 | ) : ( 113 | 114 | )} 115 |
116 |
117 | { 120 | if (isLargeScreen) return; 121 | setChatHistoryOpen(open); 122 | }} 123 | > 124 | 125 | 126 | Thread History 127 | 128 | setChatHistoryOpen((o) => !o)} 131 | /> 132 | 133 | 134 |
135 | 136 | ); 137 | } 138 | -------------------------------------------------------------------------------- /templates/nextjs/src/components/thread/markdown-styles.css: -------------------------------------------------------------------------------- 1 | /* Base markdown styles */ 2 | .markdown-content code:not(pre code) { 3 | background-color: rgba(0, 0, 0, 0.05); 4 | padding: 0.2em 0.4em; 5 | border-radius: 3px; 6 | font-size: 0.9em; 7 | } 8 | 9 | .markdown-content a { 10 | color: #0070f3; 11 | text-decoration: none; 12 | } 13 | 14 | .markdown-content a:hover { 15 | text-decoration: underline; 16 | } 17 | 18 | .markdown-content blockquote { 19 | border-left: 4px solid #ddd; 20 | padding-left: 1rem; 21 | color: #666; 22 | } 23 | 24 | .markdown-content pre { 25 | overflow-x: auto; 26 | } 27 | 28 | .markdown-content table { 29 | border-collapse: collapse; 30 | width: 100%; 31 | } 32 | 33 | .markdown-content th, 34 | .markdown-content td { 35 | border: 1px solid #ddd; 36 | padding: 8px; 37 | } 38 | 39 | .markdown-content th { 40 | background-color: #f2f2f2; 41 | } 42 | 43 | .markdown-content tr:nth-child(even) { 44 | background-color: #f9f9f9; 45 | } 46 | -------------------------------------------------------------------------------- /templates/nextjs/src/components/thread/messages/human.tsx: -------------------------------------------------------------------------------- 1 | import { useStreamContext } from "@/providers/Stream"; 2 | import { Message } from "@langchain/langgraph-sdk"; 3 | import { useState } from "react"; 4 | import { getContentString } from "../utils"; 5 | import { cn } from "@/lib/utils"; 6 | import { Textarea } from "@/components/ui/textarea"; 7 | import { BranchSwitcher, CommandBar } from "./shared"; 8 | 9 | function EditableContent({ 10 | value, 11 | setValue, 12 | onSubmit, 13 | }: { 14 | value: string; 15 | setValue: React.Dispatch>; 16 | onSubmit: () => void; 17 | }) { 18 | const handleKeyDown = (e: React.KeyboardEvent) => { 19 | if ((e.metaKey || e.ctrlKey) && e.key === "Enter") { 20 | e.preventDefault(); 21 | onSubmit(); 22 | } 23 | }; 24 | 25 | return ( 26 |