├── .codespellignore ├── .eslintrc.json ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── components.json ├── next.config.mjs ├── package.json ├── postcss.config.mjs ├── public ├── agent_inbox_diagram.png └── inbox_screenshot.png ├── src ├── app │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ └── page.tsx ├── components │ ├── agent-inbox │ │ ├── components │ │ │ ├── add-agent-inbox-dialog.tsx │ │ │ ├── agent-inbox-logo.tsx │ │ │ ├── backfill-banner.tsx │ │ │ ├── breadcrumb.tsx │ │ │ ├── dropdown-and-dialog.tsx │ │ │ ├── edit-agent-inbox-dialog.tsx │ │ │ ├── generic-inbox-item.tsx │ │ │ ├── generic-interrupt-value.tsx │ │ │ ├── inbox-buttons.tsx │ │ │ ├── inbox-item-input.tsx │ │ │ ├── inbox-item.tsx │ │ │ ├── interrupt-details-view.tsx │ │ │ ├── interrupted-inbox-item.tsx │ │ │ ├── pagination.tsx │ │ │ ├── settings-popover.tsx │ │ │ ├── state-view.tsx │ │ │ ├── statuses.tsx │ │ │ ├── thread-actions-view.tsx │ │ │ ├── thread-id.tsx │ │ │ ├── tool-call-table.tsx │ │ │ └── views │ │ │ │ ├── EmptyStateView.tsx │ │ │ │ ├── InterruptedDescriptionView.tsx │ │ │ │ ├── NonInterruptedDescriptionView.tsx │ │ │ │ ├── ThreadStateView.tsx │ │ │ │ └── index.ts │ │ ├── constants.ts │ │ ├── contexts │ │ │ ├── ThreadContext.tsx │ │ │ └── utils.ts │ │ ├── hooks │ │ │ ├── use-inboxes.tsx │ │ │ ├── use-interrupted-actions.tsx │ │ │ ├── use-local-storage.tsx │ │ │ ├── use-query-params.tsx │ │ │ └── use-scroll-position.tsx │ │ ├── inbox-view.tsx │ │ ├── index.tsx │ │ ├── thread-view.tsx │ │ ├── types.ts │ │ ├── utils.ts │ │ └── utils │ │ │ ├── backfill.ts │ │ │ └── logger.ts │ ├── app-sidebar │ │ └── index.tsx │ ├── icons │ │ └── GraphIcon.svg │ └── ui │ │ ├── alert.tsx │ │ ├── assistant-ui │ │ ├── markdown-text.tsx │ │ ├── syntax-highlighter.tsx │ │ └── tooltip-icon-button.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── button.tsx │ │ ├── checkbox.tsx │ │ ├── collapsible.tsx │ │ ├── dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── header.tsx │ │ ├── hover-card.tsx │ │ ├── icons.tsx │ │ ├── inline-context-tooltip.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── markdown-text.tsx │ │ ├── password-input.tsx │ │ ├── pill-button.tsx │ │ ├── popover.tsx │ │ ├── progress.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── sidebar.tsx │ │ ├── skeleton.tsx │ │ ├── slider.tsx │ │ ├── table.tsx │ │ ├── textarea.tsx │ │ ├── toast.tsx │ │ ├── toaster.tsx │ │ └── tooltip.tsx ├── hooks │ ├── use-mobile.tsx │ └── use-toast.ts └── lib │ ├── client.ts │ ├── convert_messages.ts │ ├── cookies.ts │ └── utils.ts ├── tailwind.config.ts ├── tsconfig.json └── yarn.lock /.codespellignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langchain-ai/agent-inbox/4da9032b44433fa11fe3b2f4323260e0b40fb513/.codespellignore -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next/core-web-vitals", 4 | "plugin:@typescript-eslint/recommended" 5 | ], 6 | "parser": "@typescript-eslint/parser", 7 | "plugins": ["@typescript-eslint", "unused-imports", "@typescript-eslint/eslint-plugin"], 8 | "rules": { 9 | "@typescript-eslint/no-unused-vars": [ 10 | "error", 11 | { 12 | "argsIgnorePattern": "^_", 13 | "varsIgnorePattern": "^_|^UNUSED_", 14 | "caughtErrorsIgnorePattern": "^_", 15 | "destructuredArrayIgnorePattern": "^_" 16 | } 17 | ], 18 | "@typescript-eslint/no-explicit-any": "off", 19 | "@typescript-eslint/no-empty-object-type": "off", 20 | "unused-imports/no-unused-imports": "error" 21 | } 22 | } -------------------------------------------------------------------------------- /.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 | - name: Use Node.js 18.x 28 | uses: actions/setup-node@v3 29 | with: 30 | node-version: 18.x 31 | cache: "yarn" 32 | - name: Install dependencies 33 | run: yarn install --immutable --mode=skip-build 34 | - name: Check formatting 35 | run: yarn format:check 36 | 37 | lint: 38 | name: Check linting 39 | runs-on: ubuntu-latest 40 | steps: 41 | - uses: actions/checkout@v4 42 | - name: Use Node.js 18.x 43 | uses: actions/setup-node@v3 44 | with: 45 | node-version: 18.x 46 | cache: "yarn" 47 | - name: Install dependencies 48 | run: yarn install --immutable --mode=skip-build 49 | - name: Check linting 50 | run: yarn run lint 51 | 52 | readme-spelling: 53 | name: Check README spelling 54 | runs-on: ubuntu-latest 55 | steps: 56 | - uses: actions/checkout@v4 57 | - uses: codespell-project/actions-codespell@v2 58 | with: 59 | ignore_words_file: .codespellignore 60 | path: README.md 61 | 62 | check-spelling: 63 | name: Check code spelling 64 | runs-on: ubuntu-latest 65 | steps: 66 | - uses: actions/checkout@v4 67 | - uses: codespell-project/actions-codespell@v2 68 | with: 69 | ignore_words_file: .codespellignore 70 | path: src 71 | -------------------------------------------------------------------------------- /.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 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | 39 | credentials.json 40 | .env 41 | .turbo -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) LangChain, Inc. 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. -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.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 | } -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "agent-inbox", 3 | "author": "Brace Sproul", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "lint:fix": "next lint --fix", 11 | "format": "prettier --config .prettierrc --write \"src\"", 12 | "format:check": "prettier --config .prettierrc --check \"src\"" 13 | }, 14 | "dependencies": { 15 | "@assistant-ui/react": "^0.5.71", 16 | "@assistant-ui/react-markdown": "^0.2.18", 17 | "@assistant-ui/react-syntax-highlighter": "^0.0.13", 18 | "@blocknote/core": "^0.17.1", 19 | "@blocknote/mantine": "^0.17.1", 20 | "@blocknote/react": "^0.17.1", 21 | "@blocknote/shadcn": "^0.17.1", 22 | "@codemirror/lang-cpp": "^6.0.2", 23 | "@codemirror/lang-html": "^6.4.9", 24 | "@codemirror/lang-java": "^6.0.1", 25 | "@codemirror/lang-javascript": "^6.2.2", 26 | "@codemirror/lang-json": "^6.0.1", 27 | "@codemirror/lang-php": "^6.0.1", 28 | "@codemirror/lang-python": "^6.1.6", 29 | "@codemirror/lang-rust": "^6.0.1", 30 | "@codemirror/lang-sql": "^6.8.0", 31 | "@codemirror/lang-xml": "^6.1.0", 32 | "@faker-js/faker": "^9.2.0", 33 | "@langchain/anthropic": "^0.3.6", 34 | "@langchain/community": "^0.3.9", 35 | "@langchain/core": "^0.3.14", 36 | "@langchain/google-genai": "^0.1.2", 37 | "@langchain/langgraph": "^0.2.23", 38 | "@langchain/langgraph-sdk": "^0.0.30", 39 | "@langchain/openai": "^0.3.11", 40 | "@nextjournal/lang-clojure": "^1.0.0", 41 | "@radix-ui/react-avatar": "^1.1.0", 42 | "@radix-ui/react-checkbox": "^1.1.2", 43 | "@radix-ui/react-collapsible": "^1.1.1", 44 | "@radix-ui/react-dialog": "^1.1.2", 45 | "@radix-ui/react-dropdown-menu": "^2.1.2", 46 | "@radix-ui/react-hover-card": "^1.1.2", 47 | "@radix-ui/react-icons": "^1.3.0", 48 | "@radix-ui/react-label": "^2.1.0", 49 | "@radix-ui/react-popover": "^1.1.2", 50 | "@radix-ui/react-progress": "^1.1.0", 51 | "@radix-ui/react-select": "^2.1.1", 52 | "@radix-ui/react-separator": "^1.1.0", 53 | "@radix-ui/react-slider": "^1.2.1", 54 | "@radix-ui/react-slot": "^1.1.0", 55 | "@radix-ui/react-toast": "^1.2.1", 56 | "@radix-ui/react-tooltip": "^1.1.4", 57 | "@replit/codemirror-lang-csharp": "^6.2.0", 58 | "@supabase/ssr": "^0.5.1", 59 | "@supabase/supabase-js": "^2.45.5", 60 | "@tanstack/react-table": "^8.20.5", 61 | "@types/react-syntax-highlighter": "^15.5.13", 62 | "@uiw/react-codemirror": "^4.23.5", 63 | "@uiw/react-md-editor": "^4.0.4", 64 | "@vercel/kv": "^2.0.0", 65 | "class-variance-authority": "^0.7.1", 66 | "clsx": "^2.1.1", 67 | "date-fns": "^4.1.0", 68 | "dotenv": "^16.4.5", 69 | "eslint-plugin-unused-imports": "^4.1.4", 70 | "framer-motion": "^11.11.9", 71 | "js-cookie": "^3.0.5", 72 | "langchain": "^0.3.5", 73 | "langsmith": "^0.1.61", 74 | "lodash": "^4.17.21", 75 | "lucide-react": "^0.468.0", 76 | "next": "14.2.25", 77 | "react": "^18", 78 | "react-colorful": "^5.6.1", 79 | "react-dom": "^18", 80 | "react-icons": "^5.3.0", 81 | "react-json-view": "^1.21.3", 82 | "react-markdown": "^9.0.1", 83 | "react-syntax-highlighter": "^15.5.0", 84 | "rehype-katex": "^7.0.1", 85 | "rehype-raw": "^7.0.0", 86 | "remark-gfm": "^4.0.0", 87 | "remark-math": "^6.0.0", 88 | "tailwind-merge": "^2.5.2", 89 | "tailwind-scrollbar-hide": "^1.1.7", 90 | "tailwindcss-animate": "^1.0.7", 91 | "uuid": "^11.0.3", 92 | "zod": "^3.23.8" 93 | }, 94 | "devDependencies": { 95 | "@eslint/js": "^9.12.0", 96 | "@types/eslint__js": "^8.42.3", 97 | "@types/js-cookie": "^3.0.6", 98 | "@types/lodash": "^4.17.12", 99 | "@types/node": "^20", 100 | "@types/react": "^18", 101 | "@types/react-dom": "^18", 102 | "@types/uuid": "^10.0.0", 103 | "@typescript-eslint/eslint-plugin": "^8.12.2", 104 | "@typescript-eslint/parser": "^8.8.1", 105 | "eslint": "^8", 106 | "eslint-config-next": "14.2.10", 107 | "postcss": "^8", 108 | "prettier": "^3.3.3", 109 | "tailwind-scrollbar": "^3.1.0", 110 | "tailwindcss": "^3.4.1", 111 | "tsx": "^4.19.1", 112 | "typescript": "^5", 113 | "typescript-eslint": "^8.8.1" 114 | }, 115 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" 116 | } 117 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /public/agent_inbox_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langchain-ai/agent-inbox/4da9032b44433fa11fe3b2f4323260e0b40fb513/public/agent_inbox_diagram.png -------------------------------------------------------------------------------- /public/inbox_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langchain-ai/agent-inbox/4da9032b44433fa11fe3b2f4323260e0b40fb513/public/inbox_screenshot.png -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langchain-ai/agent-inbox/4da9032b44433fa11fe3b2f4323260e0b40fb513/src/app/favicon.ico -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Fira+Code&family=Inter:wght@400;500;600;700&display=swap"); 2 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"); 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | 7 | :root { 8 | --foreground-rgb: 0, 0, 0; 9 | --background-start-rgb: 214, 219, 220; 10 | --background-end-rgb: 255, 255, 255; 11 | --row-height: 53px; 12 | } 13 | 14 | @media (prefers-color-scheme: dark) { 15 | :root { 16 | --foreground-rgb: 255, 255, 255; 17 | --background-start-rgb: 0, 0, 0; 18 | --background-end-rgb: 0, 0, 0; 19 | } 20 | } 21 | *::-webkit-scrollbar { 22 | display: none; 23 | } 24 | 25 | a { 26 | color: rgb(33, 118, 246); 27 | } 28 | 29 | @layer utilities { 30 | .text-balance { 31 | text-wrap: balance; 32 | } 33 | .no-scrollbar::-webkit-scrollbar { 34 | display: none; 35 | } 36 | 37 | .no-scrollbar { 38 | -ms-overflow-style: none; /* IE and Edge */ 39 | scrollbar-width: none; /* Firefox */ 40 | } 41 | 42 | .shadow-inner-right { 43 | box-shadow: inset -9px 0 6px -1px rgb(0 0 0 / 0.02); 44 | } 45 | 46 | .shadow-inner-left { 47 | box-shadow: inset 9px 0 6px -1px rgb(0 0 0 / 0.02); 48 | } 49 | } 50 | 51 | @keyframes glow { 52 | 0%, 53 | 100% { 54 | box-shadow: 0 0 8px rgba(34, 197, 94, 0.6); 55 | } 56 | 50% { 57 | box-shadow: 0 0 24px rgba(34, 197, 94, 1); 58 | } 59 | } 60 | 61 | @layer base { 62 | :root { 63 | --background: 220 14% 98%; 64 | --foreground: 0 0% 3.9%; 65 | --card: 0 0% 100%; 66 | --card-foreground: 0 0% 3.9%; 67 | --popover: 0 0% 100%; 68 | --popover-foreground: 0 0% 3.9%; 69 | --primary: 0 0% 9%; 70 | --primary-foreground: 0 0% 98%; 71 | --secondary: 0 0% 96.1%; 72 | --secondary-foreground: 0 0% 9%; 73 | --muted: 0 0% 96.1%; 74 | --muted-foreground: 0 0% 45.1%; 75 | --accent: 0 0% 96.1%; 76 | --accent-foreground: 0 0% 9%; 77 | --destructive: 0 84.2% 60.2%; 78 | --destructive-foreground: 0 0% 98%; 79 | --border: 0 0% 89.8%; 80 | --input: 0 0% 89.8%; 81 | --ring: 0 0% 3.9%; 82 | --chart-1: 12 76% 61%; 83 | --chart-2: 173 58% 39%; 84 | --chart-3: 197 37% 24%; 85 | --chart-4: 43 74% 66%; 86 | --chart-5: 27 87% 67%; 87 | --radius: 0.5rem; 88 | --sidebar-background: 0 0% 98%; 89 | --sidebar-foreground: 240 5.3% 26.1%; 90 | --sidebar-primary: 240 5.9% 10%; 91 | --sidebar-primary-foreground: 0 0% 98%; 92 | --sidebar-accent: 240 4.8% 95.9%; 93 | --sidebar-accent-foreground: 240 5.9% 10%; 94 | --sidebar-border: 220 13% 91%; 95 | --sidebar-ring: 217.2 91.2% 59.8%; 96 | } 97 | .dark { 98 | --background: 0 0% 3.9%; 99 | --foreground: 0 0% 98%; 100 | --card: 0 0% 3.9%; 101 | --card-foreground: 0 0% 98%; 102 | --popover: 0 0% 3.9%; 103 | --popover-foreground: 0 0% 98%; 104 | --primary: 0 0% 98%; 105 | --primary-foreground: 0 0% 9%; 106 | --secondary: 0 0% 14.9%; 107 | --secondary-foreground: 0 0% 98%; 108 | --muted: 0 0% 14.9%; 109 | --muted-foreground: 0 0% 63.9%; 110 | --accent: 0 0% 14.9%; 111 | --accent-foreground: 0 0% 98%; 112 | --destructive: 0 62.8% 30.6%; 113 | --destructive-foreground: 0 0% 98%; 114 | --border: 0 0% 14.9%; 115 | --input: 0 0% 14.9%; 116 | --ring: 0 0% 83.1%; 117 | --chart-1: 220 70% 50%; 118 | --chart-2: 160 60% 45%; 119 | --chart-3: 30 80% 55%; 120 | --chart-4: 280 65% 60%; 121 | --chart-5: 340 75% 55%; 122 | --sidebar-background: 240 5.9% 10%; 123 | --sidebar-foreground: 240 4.8% 95.9%; 124 | --sidebar-primary: 224.3 76.3% 48%; 125 | --sidebar-primary-foreground: 0 0% 100%; 126 | --sidebar-accent: 240 3.7% 15.9%; 127 | --sidebar-accent-foreground: 240 4.8% 95.9%; 128 | --sidebar-border: 240 3.7% 15.9%; 129 | --sidebar-ring: 217.2 91.2% 59.8%; 130 | } 131 | html { 132 | font-family: 133 | "Inter", 134 | -apple-system, 135 | BlinkMacSystemFont, 136 | "Segoe UI", 137 | Roboto, 138 | Helvetica, 139 | Arial, 140 | sans-serif, 141 | "Apple Color Emoji", 142 | "Segoe UI Emoji", 143 | "Segoe UI Symbol"; 144 | } 145 | } 146 | 147 | @layer base { 148 | * { 149 | @apply border-border; 150 | } 151 | body { 152 | @apply bg-background text-foreground; 153 | } 154 | } 155 | 156 | /* Change highlight color */ 157 | ::selection { 158 | background-color: rgba(53, 151, 147, 0.3); 159 | } 160 | 161 | ::-moz-selection { 162 | background-color: rgba(53, 151, 147, 0.3); 163 | } 164 | 165 | @layer utilities { 166 | .shadow-inner-right { 167 | box-shadow: inset -9px 0 6px -1px rgb(0 0 0 / 0.02); 168 | } 169 | } 170 | 171 | .artifact-content { 172 | line-height: calc( 173 | 1.625em + 2px 174 | ); /* Adjust the base value (1.625em) if needed */ 175 | } 176 | 177 | .inline-code { 178 | font-family: monospace; 179 | background-color: #f0f0f0; 180 | padding: 2px 4px; 181 | border-radius: 4px; 182 | font-size: 0.9em; 183 | } 184 | 185 | tr { 186 | height: var(--row-height); 187 | } 188 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import "./globals.css"; 3 | import { Inter } from "next/font/google"; 4 | import { Toaster } from "@/components/ui/toaster"; 5 | import { ThreadsProvider } from "@/components/agent-inbox/contexts/ThreadContext"; 6 | import React from "react"; 7 | import { SidebarProvider } from "@/components/ui/sidebar"; 8 | import { AppSidebar, AppSidebarTrigger } from "@/components/app-sidebar"; 9 | import { BreadCrumb } from "@/components/agent-inbox/components/breadcrumb"; 10 | import { cn } from "@/lib/utils"; 11 | 12 | const inter = Inter({ 13 | subsets: ["latin"], 14 | preload: true, 15 | display: "swap", 16 | }); 17 | 18 | export const metadata: Metadata = { 19 | title: "Agent Inbox", 20 | description: "Agent Inbox UX by LangChain", 21 | }; 22 | 23 | export default function RootLayout({ 24 | children, 25 | }: Readonly<{ 26 | children: React.ReactNode; 27 | }>) { 28 | return ( 29 | 30 | 31 | Loading (layout)...}> 32 | 33 | 34 | 35 | 36 |
37 | 38 |
39 | 40 |
46 | {children} 47 |
48 |
49 |
50 |
51 |
52 |
53 | 54 | 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { AgentInbox } from "@/components/agent-inbox"; 4 | import React from "react"; 5 | 6 | export default function DemoPage(): React.ReactNode { 7 | return ( 8 |
9 | 10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/components/agent-inbox/components/agent-inbox-logo.tsx: -------------------------------------------------------------------------------- 1 | export const agentInboxSvg = ( 2 | 9 | 15 | 19 | 23 | 24 | ); 25 | -------------------------------------------------------------------------------- /src/components/agent-inbox/components/backfill-banner.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "@/components/ui/button"; 4 | import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; 5 | import { InfoCircledIcon, ReloadIcon } from "@radix-ui/react-icons"; 6 | import { useToast } from "@/hooks/use-toast"; 7 | import { useState, useEffect } from "react"; 8 | import { 9 | forceInboxBackfill, 10 | isBackfillCompleted, 11 | markBackfillCompleted, 12 | } from "../utils/backfill"; 13 | import { logger } from "../utils/logger"; 14 | 15 | export function BackfillBanner() { 16 | const [mounted, setMounted] = useState(false); 17 | const [showBanner, setShowBanner] = useState(false); 18 | const [isRunning, setIsRunning] = useState(false); 19 | const { toast } = useToast(); 20 | 21 | // Only run this check after mounting on the client 22 | useEffect(() => { 23 | setMounted(true); 24 | setShowBanner(!isBackfillCompleted()); 25 | }, []); 26 | 27 | // Don't render anything during SSR or until the component has mounted 28 | if (!mounted || !showBanner) { 29 | return null; 30 | } 31 | 32 | const handleRunBackfill = async () => { 33 | setIsRunning(true); 34 | try { 35 | const result = await forceInboxBackfill(); 36 | 37 | if (result.success) { 38 | toast({ 39 | title: "Success", 40 | description: 41 | "Your inbox IDs have been updated. Please refresh the page to see your inboxes.", 42 | duration: 5000, 43 | }); 44 | setShowBanner(false); 45 | } else { 46 | toast({ 47 | title: "Error", 48 | description: 49 | "Failed to update inbox IDs. Please try again or contact support.", 50 | variant: "destructive", 51 | duration: 5000, 52 | }); 53 | } 54 | } catch (error) { 55 | logger.error("Error running backfill:", error); 56 | toast({ 57 | title: "Error", 58 | description: "An unexpected error occurred. Please try again later.", 59 | variant: "destructive", 60 | duration: 5000, 61 | }); 62 | } finally { 63 | setIsRunning(false); 64 | } 65 | }; 66 | 67 | const handleDismiss = () => { 68 | markBackfillCompleted(); 69 | setShowBanner(false); 70 | toast({ 71 | title: "Dismissed", 72 | description: 73 | "The banner has been dismissed. You can still update your inboxes from settings.", 74 | duration: 3000, 75 | }); 76 | }; 77 | 78 | return ( 79 | 80 | 81 | Update Your Inboxes 82 | 83 |

84 | We have updated how inbox IDs are generated to better support sharing 85 | links across machines. Your existing inboxes need to be updated. 86 |

87 |
88 | 97 | 105 |
106 |
107 |
108 | ); 109 | } 110 | -------------------------------------------------------------------------------- /src/components/agent-inbox/components/breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import NextLink from "next/link"; 4 | import { Button } from "@/components/ui/button"; 5 | import { cn } from "@/lib/utils"; 6 | import { ChevronRight } from "lucide-react"; 7 | import { useQueryParams } from "../hooks/use-query-params"; 8 | import { 9 | AGENT_INBOX_PARAM, 10 | IMPROPER_SCHEMA, 11 | INBOX_PARAM, 12 | VIEW_STATE_THREAD_QUERY_PARAM, 13 | } from "../constants"; 14 | import { HumanInterrupt, ThreadStatusWithAll } from "../types"; 15 | import { prettifyText } from "../utils"; 16 | import { useThreadsContext } from "../contexts/ThreadContext"; 17 | import React from "react"; 18 | import { logger } from "../utils/logger"; 19 | 20 | export function BreadCrumb({ className }: { className?: string }) { 21 | const { searchParams } = useQueryParams(); 22 | const { threadData, agentInboxes } = useThreadsContext(); 23 | const [agentInboxLabel, setAgentInboxLabel] = React.useState(); 24 | const [selectedInboxLabel, setSelectedInboxLabel] = React.useState(); 25 | const [selectedThreadActionLabel, setSelectedThreadActionLabel] = 26 | React.useState(); 27 | 28 | React.useEffect(() => { 29 | try { 30 | const selectedAgentInbox = agentInboxes.find((a) => a.selected); 31 | if (selectedAgentInbox) { 32 | const selectedAgentInboxLabel = 33 | selectedAgentInbox.name || prettifyText(selectedAgentInbox.graphId); 34 | setAgentInboxLabel(selectedAgentInboxLabel); 35 | } else { 36 | setAgentInboxLabel(undefined); 37 | } 38 | 39 | const selectedInboxParam = searchParams.get(INBOX_PARAM) as 40 | | ThreadStatusWithAll 41 | | undefined; 42 | if (selectedInboxParam) { 43 | setSelectedInboxLabel(prettifyText(selectedInboxParam)); 44 | } else { 45 | setSelectedInboxLabel(undefined); 46 | } 47 | 48 | const selectedThreadIdParam = searchParams.get( 49 | VIEW_STATE_THREAD_QUERY_PARAM 50 | ); 51 | const selectedThread = threadData.find( 52 | (t) => t.thread.thread_id === selectedThreadIdParam 53 | ); 54 | const selectedThreadAction = ( 55 | selectedThread?.interrupts as HumanInterrupt[] | undefined 56 | )?.[0]?.action_request?.action; 57 | if (selectedThreadAction) { 58 | if (selectedThreadAction === IMPROPER_SCHEMA) { 59 | setSelectedThreadActionLabel("Interrupt"); 60 | } else { 61 | setSelectedThreadActionLabel(prettifyText(selectedThreadAction)); 62 | } 63 | } else { 64 | setSelectedThreadActionLabel(undefined); 65 | } 66 | } catch (e) { 67 | logger.error("Error while updating breadcrumb", e); 68 | } 69 | }, [searchParams, agentInboxes, threadData]); 70 | 71 | const constructBaseUrl = () => { 72 | const selectedAgentInbox = agentInboxes.find((a) => a.selected); 73 | if (!selectedAgentInbox) { 74 | return "/"; 75 | } 76 | return `/?${AGENT_INBOX_PARAM}=${selectedAgentInbox.id}`; 77 | }; 78 | 79 | const constructInboxLink = () => { 80 | const currentUrl = new URL(window.location.href); 81 | currentUrl.searchParams.delete(VIEW_STATE_THREAD_QUERY_PARAM); 82 | return `${currentUrl.pathname}${currentUrl.search}`; 83 | }; 84 | 85 | if (!agentInboxLabel) { 86 | return ( 87 |