├── .dockerignore
├── .eslintrc.json
├── .eslintrc.verbose.json
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
└── workflows
│ ├── build-main.yml
│ └── ci.yml
├── .gitignore
├── .husky
└── pre-commit
├── .npmrc
├── .prettierignore
├── .prettierrc
├── Dockerfile
├── LICENSE
├── README.md
├── assets
├── chat.png
├── collections.png
├── documents.png
└── login.png
├── components.json
├── next-env.d.ts
├── next.config.js
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── public
├── env-config.js
├── favicon.ico
└── images
│ ├── default_profile.svg
│ ├── favicon.ico
│ ├── github-mark.svg
│ ├── google-logo.svg
│ ├── hatchet-logo.svg
│ ├── javascript-logo.svg
│ ├── python-logo.svg
│ └── sciphi.svg
├── sentry.client.config.ts
├── sentry.edge.config.ts
├── sentry.server.config.ts
├── src
├── components
│ ├── AverageScoreCard.tsx
│ ├── ChatDemo
│ │ ├── ConfigurationSheet.tsx
│ │ ├── CreateDialog.tsx
│ │ ├── DefaultQueries.tsx
│ │ ├── DocumentsTable.tsx
│ │ ├── DownloadFileContainer.tsx
│ │ ├── ExtractContainer.tsx
│ │ ├── KGDescriptionDialog.tsx
│ │ ├── MessageBubble.tsx
│ │ ├── PipelineStatus.tsx
│ │ ├── ServerCard.tsx
│ │ ├── SingleSwitch.tsx
│ │ ├── SwitchManager.tsx
│ │ ├── Table.tsx
│ │ ├── UpdateButtonContainer.tsx
│ │ ├── UploadDialog.tsx
│ │ ├── answer.tsx
│ │ ├── createFile.tsx
│ │ ├── deleteButton.tsx
│ │ ├── popover.tsx
│ │ ├── remove.tsx
│ │ ├── result.tsx
│ │ ├── search.tsx
│ │ ├── skeleton.tsx
│ │ ├── title.tsx
│ │ ├── update.tsx
│ │ ├── upload.tsx
│ │ └── utils
│ │ │ ├── AssignDocumentToCollectionDialog.tsx
│ │ │ ├── AssignUserToCollectionDialog.tsx
│ │ │ ├── cn.ts
│ │ │ ├── collectionCreationModal.tsx
│ │ │ ├── collectionDialog.tsx
│ │ │ ├── containerObjectCreationModal.tsx
│ │ │ ├── documentDialogInfo.tsx
│ │ │ ├── documentSorter.ts
│ │ │ ├── editPromptDialog.tsx
│ │ │ ├── formatUptime.ts
│ │ │ ├── get-search-url.ts
│ │ │ └── userDialogInfo.tsx
│ ├── ContainerObjectCard.tsx
│ ├── DAUCard.tsx
│ ├── ErrorsCard.tsx
│ ├── Graph.txt
│ ├── LLMCompletionCard.tsx
│ ├── Layout
│ │ └── index.tsx
│ ├── OverlayWrapper.tsx
│ ├── RequestsCard.tsx
│ ├── SearchResults
│ │ └── index.tsx
│ ├── Sidebar.tsx
│ ├── Spinner.tsx
│ ├── ThemeProvider
│ │ └── index.tsx
│ ├── USMapCard.tsx
│ ├── UserForm.tsx
│ ├── WAUCard.tsx
│ ├── WorldMapCard.tsx
│ ├── knowledgeGraph.tsx
│ ├── knowledgeGraphD3.tsx
│ ├── shared
│ │ ├── Footer.tsx
│ │ ├── Logo.tsx
│ │ ├── NavBar.tsx
│ │ └── ThemeToggle.tsx
│ ├── ui
│ │ ├── BarChart.tsx
│ │ ├── Button.tsx
│ │ ├── CopyableContent.tsx
│ │ ├── InfoIcon.tsx
│ │ ├── ModelSelector.tsx
│ │ ├── ShadcnButton.tsx
│ │ ├── UserSelector.tsx
│ │ ├── WatchButton.tsx
│ │ ├── accordion.tsx
│ │ ├── alert-dialog.tsx
│ │ ├── alert.tsx
│ │ ├── avatar.tsx
│ │ ├── badge.tsx
│ │ ├── card.tsx
│ │ ├── carousel.tsx
│ │ ├── checkbox.tsx
│ │ ├── collapsible.tsx
│ │ ├── dialog.tsx
│ │ ├── dropdown-menu.tsx
│ │ ├── highlight.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── logtable.tsx
│ │ ├── menubar.tsx
│ │ ├── multi-select.tsx
│ │ ├── pagination.tsx
│ │ ├── popover.tsx
│ │ ├── progress.tsx
│ │ ├── select.tsx
│ │ ├── separator.tsx
│ │ ├── sheet.tsx
│ │ ├── slider.tsx
│ │ ├── switch.tsx
│ │ ├── table.tsx
│ │ ├── tabs.tsx
│ │ ├── textarea.tsx
│ │ ├── toast.tsx
│ │ ├── toaster.tsx
│ │ ├── tooltip.tsx
│ │ └── use-toast.ts
│ ├── us-abbr.json
│ ├── usa-topo.json
│ └── world-topo.json
├── config
│ ├── brandingConfig.ts
│ └── brandingOverride.ts
├── context
│ └── UserContext.tsx
├── hooks
│ └── usePagination.ts
├── instrumentation.ts
├── lib
│ ├── CustomErrors.ts
│ ├── debounce.ts
│ ├── posthog-client.tsx
│ ├── remToPx.ts
│ ├── supabase.ts
│ └── utils.ts
├── pages
│ ├── _app.tsx
│ ├── _error.jsx
│ ├── account.tsx
│ ├── analytics.tsx
│ ├── api
│ │ └── sentry-example-api.js
│ ├── auth
│ │ ├── login.tsx
│ │ └── signup.tsx
│ ├── chat.tsx
│ ├── collections.tsx
│ ├── collections
│ │ └── [id].tsx
│ ├── documents.tsx
│ ├── error.tsx
│ ├── index.tsx
│ ├── indexLogs.tsx
│ ├── search.tsx
│ ├── settings.tsx
│ └── users.tsx
├── styles
│ ├── Index.module.scss
│ └── globals.css
└── types.ts
├── startup.sh
├── tailwind.config.js
├── tsconfig.json
└── types
└── globals.d.ts
/.dockerignore:
--------------------------------------------------------------------------------
1 | /assets
2 | node_modules
3 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["import", "@typescript-eslint"],
3 | "extends": [
4 | "next/core-web-vitals",
5 | "plugin:prettier/recommended",
6 | "plugin:@typescript-eslint/recommended"
7 | ],
8 | "rules": {
9 | "@typescript-eslint/no-explicit-any": "off",
10 | "@typescript-eslint/no-var-requires": "off",
11 | "no-unused-vars": "off",
12 | "@typescript-eslint/no-unused-vars": "off",
13 | "@next/next/no-page-custom-font": "off",
14 | "react-hooks/exhaustive-deps": "off",
15 | "react/no-unescaped-entities": "off",
16 | "@next/next/no-img-element": "off",
17 | "jsx-a11y/alt-text": "off",
18 | "import/order": [
19 | "error",
20 | {
21 | "groups": [
22 | "builtin",
23 | "external",
24 | "internal",
25 | "parent",
26 | "sibling",
27 | "index"
28 | ],
29 | "pathGroups": [
30 | {
31 | "pattern": "@/components/**",
32 | "group": "internal"
33 | },
34 | {
35 | "pattern": "@/ui/**",
36 | "group": "internal"
37 | }
38 | ],
39 | "pathGroupsExcludedImportTypes": ["builtin"],
40 | "newlines-between": "always",
41 | "alphabetize": {
42 | "order": "asc",
43 | "caseInsensitive": true
44 | }
45 | }
46 | ]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/.eslintrc.verbose.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["./.eslintrc.json"],
3 | "rules": {
4 | "@typescript-eslint/no-unused-vars": "warn",
5 | "no-unused-vars": "warn",
6 | "react-hooks/exhaustive-deps": "warn",
7 | "@next/next/no-img-element": "warn",
8 | "jsx-a11y/alt-text": "warn"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 | ---
8 |
9 | **Describe the bug**
10 | A clear and concise description of what the bug is.
11 |
12 | **To Reproduce**
13 | Steps to reproduce the behavior:
14 |
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 |
28 | - OS: [e.g. iOS]
29 | - Browser [e.g. chrome, safari]
30 | - Version [e.g. 22]
31 |
32 | **Smartphone (please complete the following information):**
33 |
34 | - Device: [e.g. iPhone6]
35 | - OS: [e.g. iOS8.1]
36 | - Browser [e.g. stock browser, safari]
37 | - Version [e.g. 22]
38 |
39 | **Additional context**
40 | Add any other context about the problem here.
41 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for the R2R Dashboard!
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 | ---
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11 |
12 | **Describe the solution you'd like**
13 | A clear and concise description of what you want to happen.
14 |
15 | **Describe alternatives you've considered**
16 | A clear and concise description of any alternative solutions or features you've considered.
17 |
18 | **Additional context**
19 | Add any other context or screenshots about the feature request here.
20 |
--------------------------------------------------------------------------------
/.github/workflows/build-main.yml:
--------------------------------------------------------------------------------
1 | name: Publish Docker image
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | version:
7 | description: 'Version to publish (leave empty to use package.json version)'
8 | required: false
9 | type: string
10 |
11 | env:
12 | REGISTRY_IMAGE: sciphiai/r2r-dashboard
13 |
14 | jobs:
15 | prepare:
16 | runs-on: ubuntu-latest
17 | outputs:
18 | release_version: ${{ steps.version.outputs.RELEASE_VERSION }}
19 | matrix: ${{ steps.set-matrix.outputs.matrix }}
20 | steps:
21 | - name: Check out the repo
22 | uses: actions/checkout@v4
23 |
24 | - name: Get version from package.json
25 | id: package-version
26 | run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
27 |
28 | - name: Determine version to use
29 | id: version
30 | run: |
31 | if [ -n "${{ github.event.inputs.version }}" ]; then
32 | echo "RELEASE_VERSION=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
33 | else
34 | echo "RELEASE_VERSION=${{ steps.package-version.outputs.VERSION }}" >> $GITHUB_OUTPUT
35 | fi
36 |
37 | - name: Set matrix
38 | id: set-matrix
39 | run: |
40 | echo "matrix={\"include\":[{\"platform\":\"amd64\",\"runner\":\"ubuntu-latest\"},{\"platform\":\"arm64\",\"runner\":\"arm64\"}]}" >> $GITHUB_OUTPUT
41 |
42 | build:
43 | needs: prepare
44 | strategy:
45 | fail-fast: false
46 | matrix: ${{fromJson(needs.prepare.outputs.matrix)}}
47 | runs-on: ${{ matrix.runner }}
48 | permissions:
49 | packages: write
50 | contents: read
51 | id-token: write
52 | actions: write
53 | steps:
54 | - name: Check out the repo
55 | uses: actions/checkout@v4
56 |
57 | - name: Set up Docker Buildx
58 | uses: docker/setup-buildx-action@v3
59 |
60 | - name: Log in to Docker Hub
61 | uses: docker/login-action@v3
62 | with:
63 | username: ${{ secrets.DOCKER_USERNAME }}
64 | password: ${{ secrets.DOCKER_TOKEN }}
65 |
66 | - name: Build and push Docker image
67 | uses: docker/build-push-action@v5
68 | with:
69 | context: .
70 | file: ./Dockerfile
71 | platforms: linux/${{ matrix.platform }}
72 | push: true
73 | tags: ${{ env.REGISTRY_IMAGE }}:${{ needs.prepare.outputs.release_version }}-${{ matrix.platform }}
74 | provenance: false
75 | sbom: false
76 |
77 | create-manifest:
78 | needs: [prepare, build]
79 | runs-on: ubuntu-latest
80 | steps:
81 | - name: Log in to Docker Hub
82 | uses: docker/login-action@v3
83 | with:
84 | username: ${{ secrets.DOCKER_USERNAME }}
85 | password: ${{ secrets.DOCKER_TOKEN }}
86 |
87 | - name: Create and push manifest
88 | run: |
89 | docker buildx imagetools create -t ${{ env.REGISTRY_IMAGE }}:${{ needs.prepare.outputs.release_version }} \
90 | ${{ env.REGISTRY_IMAGE }}:${{ needs.prepare.outputs.release_version }}-amd64 \
91 | ${{ env.REGISTRY_IMAGE }}:${{ needs.prepare.outputs.release_version }}-arm64
92 |
93 | docker buildx imagetools create -t ${{ env.REGISTRY_IMAGE }}:latest \
94 | ${{ env.REGISTRY_IMAGE }}:${{ needs.prepare.outputs.release_version }}-amd64 \
95 | ${{ env.REGISTRY_IMAGE }}:${{ needs.prepare.outputs.release_version }}-arm64
96 |
97 | - name: Verify manifests
98 | run: |
99 | docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ needs.prepare.outputs.release_version }}
100 | docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:latest
101 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | pull_request:
5 | branches: [main]
6 | push:
7 | branches: [main]
8 |
9 | jobs:
10 | build-and-test:
11 | runs-on: ubuntu-latest
12 | strategy:
13 | matrix:
14 | node-version: [18, 19, 20, 21, 22, 23]
15 | fail-fast: false
16 |
17 | steps:
18 | - uses: actions/checkout@v4
19 |
20 | - name: Setup Node.js ${{ matrix.node-version }}
21 | uses: actions/setup-node@v4
22 | with:
23 | node-version: ${{ matrix.node-version }}
24 |
25 | - name: Install pnpm
26 | uses: pnpm/action-setup@v2
27 | with:
28 | version: latest
29 |
30 | - name: Install dependencies
31 | run: pnpm install
32 |
33 | - name: Check formatting
34 | run: pnpm format:check
35 |
36 | - name: Build
37 | run: pnpm build
38 |
39 | - name: Start server and run for 20 seconds
40 | run: |
41 | timeout 20s pnpm start || exit 0
42 |
43 | - name: Check server logs
44 | run: |
45 | if grep -q "error" .next/server.log; then
46 | echo "Server encountered errors during startup"
47 | exit 1
48 | fi
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 | .env*
3 | .DS_Store
4 |
5 | #vscode
6 | .vscode/settings.json
7 |
8 | #logs
9 | logs.json
10 |
11 | # dependencies
12 | /node_modules
13 | /cloud-docs/node_modules
14 | /.pnp
15 | .pnp.js
16 | .next
17 |
18 | # testing
19 | /coverage
20 |
21 | # next.js
22 | /.next/
23 | /out/
24 |
25 | # production
26 | /build
27 |
28 | # misc
29 | .DS_Store
30 | *.pem
31 |
32 | # debug
33 | npm-debug.log*
34 | yarn-debug.log*
35 | yarn-error.log*
36 |
37 | # local env files
38 | .env.local
39 | .env.development.local
40 | .env.test.local
41 | .env.production.local
42 |
43 | # typescript
44 | *.tsbuildinfo
45 | .vercel
46 |
47 | # Sentry Config File
48 | .env.sentry-build-plugin
49 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npx lint-staged
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | public-hoist-pattern[]=*@nextui-org/*
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .next
3 | pnpm-lock.yaml
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "trailingComma": "es5",
4 | "singleQuote": true,
5 | "printWidth": 80,
6 | "tabWidth": 2
7 | }
8 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Builder Stage
2 | FROM node:22-alpine AS builder
3 | WORKDIR /app
4 |
5 | # Install pnpm
6 | RUN npm install -g pnpm
7 |
8 | # Copy package.json and pnpm-lock.yaml
9 | COPY package.json pnpm-lock.yaml* ./
10 |
11 | # Install dependencies
12 | RUN pnpm install --frozen-lockfile
13 |
14 | # Copy the rest of the application code
15 | COPY . .
16 |
17 | # Build the Next.js application
18 | RUN pnpm build
19 |
20 | # Production Stage
21 | FROM node:22-alpine AS runner
22 | WORKDIR /app
23 |
24 | # Install pnpm
25 | RUN npm install -g pnpm
26 |
27 | # Set node environment to production
28 | ENV NODE_ENV=production
29 | ENV HOSTNAME="0.0.0.0"
30 |
31 | # Copy necessary files from builder stage
32 | COPY --from=builder /app/next.config.js ./
33 | COPY --from=builder /app/public ./public
34 | COPY --from=builder /app/.next/standalone ./
35 | COPY --from=builder /app/.next/static ./.next/static
36 |
37 | # Copy the startup script
38 | COPY startup.sh /app/startup.sh
39 |
40 | # Ensure the startup script is executable
41 | RUN chmod +x /app/startup.sh
42 |
43 | # Expose the port the app runs on
44 | EXPOSE 3000
45 |
46 | # Define the command to run the startup script
47 | CMD ["/app/startup.sh"]
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2021 Acme Co.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | R2R Dashboard
3 |
4 |
5 |
6 | Manage and Monitor Your R2R RAG Applications with Ease
7 |
8 |
9 | # About
10 |
11 | The R2R Dashboard is an open-source React+Next.js application designed to provide [R2R](https://github.com/SciPhi-AI/R2R) developers with an easy interface to interact with their pipelines. This dashboard aims to reduce development and iteration time by offering a user-friendly environment.
12 |
13 | ## Key Features
14 |
15 | - **🗂️ Document Management**: Upload, update, and delete documents and their metadata.
16 | - **🛝 Playground**: Stream RAG responses with different models and configurable settings.
17 | - **📊 Analytics**: View aggregate statistics around latencies and metrics with detailed histograms.
18 | - **📜 Logs**: Track user queries, search results, and LLM responses.
19 | - **🔧 Development Tools**: Easily start a development server, format code, and run lint checks.
20 |
21 | ## Table of Contents
22 |
23 | 1. [Quick Install](#quick-install)
24 | 2. [Links](#links)
25 | 3. [Screenshots](#screenshots)
26 | 4. [Core Abstractions](#core-abstractions)
27 | 5. [Summary](#summary)
28 |
29 | # Quick Install
30 |
31 | ### Install PNPM
32 |
33 | PNPM is a fast, disk space-efficient package manager that helps you manage your project dependencies. To install PNPM, visit the [official PNPM installation page](https://pnpm.io/installation) for the latest instructions, or follow the instructions outlined below:
34 |
35 |
36 | PNPM Installation
37 |
38 | For Unix-based systems (Linux, macOS):
39 |
40 | ```bash
41 | curl -fsSL https://get.pnpm.io/install.sh | sh -
42 | ```
43 |
44 | For Windows:
45 |
46 | ```powershell
47 | iwr https://get.pnpm.io/install.ps1 -useb | iex
48 | ```
49 |
50 | After installing PNPM, you may need to add it to your system's PATH. Follow the instructions provided on the PNPM installation page to ensure it's properly set up.
51 |
52 |
53 |
54 | ### Clone the R2R Dashboard and Install Dependencies
55 |
56 | 1. **Clone the project repository and navigate to the project directory:**
57 |
58 | ```bash
59 | git clone git@github.com:SciPhi-AI/R2R-Application.git
60 | cd R2R-Application
61 | ```
62 |
63 | 2. **Install the project dependencies using PNPM:**
64 |
65 | ```bash
66 | pnpm install
67 | ```
68 |
69 | 3. **Build and start the application for production:**
70 |
71 | ```bash
72 | pnpm build
73 | pnpm start
74 | ```
75 |
76 | This will build the application on port 3000. After `pnpm start` runs successfully, the dashboard can be viewed at [http://localhost:3000](http://localhost:3000).
77 |
78 | ### Developing with the R2R Dashboard
79 |
80 | If you'd like to develop the R2R dashboard, you can do so by starting a development server:
81 |
82 | 1. **Start the development server:**
83 |
84 | ```bash
85 | pnpm dev
86 | ```
87 |
88 | 2. **Pre-commit checks (optional but recommended):**
89 |
90 | Ensure your code is properly formatted and free of linting issues before committing:
91 |
92 | ```bash
93 | pnpm format
94 | pnpm lint
95 | ```
96 |
97 | # Links
98 |
99 | - [Join the Discord server](https://discord.gg/p6KqD2kjtB)
100 | - [R2R Docs Quickstart](https://r2r-docs.sciphi.ai/getting-started/quick-install)
101 |
102 | ## Docs
103 |
104 | - [R2R Dashboard](https://r2r-docs.sciphi.ai/cookbooks/dashboard): A how-to guide on connecting with the R2R Dashboard.
105 | - [R2R Demo](https://r2r-docs.sciphi.ai/getting-started/r2r-demo): A basic demo script designed to get you started with an R2R RAG application.
106 | - [R2R Client-Server](https://r2r-docs.sciphi.ai/cookbooks/client-server): An extension of the basic `R2R Demo` with client-server interactions.
107 | - [Local RAG](https://r2r-docs.sciphi.ai/cookbooks/local-rag): A quick cookbook demonstration of how to run R2R with local LLMs.
108 | - [Hybrid Search](https://r2r-docs.sciphi.ai/cookbooks/hybrid-search): A brief introduction to running hybrid search with R2R.
109 | - [Reranking](https://r2r-docs.sciphi.ai/cookbooks/rerank-search): A short guide on how to apply reranking to R2R results.
110 | - [SciPhi Cloud Docs](https://docs.sciphi.ai/): SciPhi Cloud documentation.
111 |
112 | # Screenshots
113 |
114 | 
115 | 
116 | 
117 | 
118 |
119 | # Summary
120 |
121 | The R2R Dashboard is a comprehensive tool designed to streamline the management and monitoring of Retrieval-Augmented Generation (RAG) pipelines built with the R2R framework. By providing a user-friendly interface and robust core features, the dashboard helps developers efficiently interact with their RAG systems, enhancing development and operational workflows.
122 |
--------------------------------------------------------------------------------
/assets/chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciPhi-AI/R2R-Application/c4612ab48af002d4cd078659d11db88ed61d53b8/assets/chat.png
--------------------------------------------------------------------------------
/assets/collections.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciPhi-AI/R2R-Application/c4612ab48af002d4cd078659d11db88ed61d53b8/assets/collections.png
--------------------------------------------------------------------------------
/assets/documents.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciPhi-AI/R2R-Application/c4612ab48af002d4cd078659d11db88ed61d53b8/assets/documents.png
--------------------------------------------------------------------------------
/assets/login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciPhi-AI/R2R-Application/c4612ab48af002d4cd078659d11db88ed61d53b8/assets/login.png
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": false,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "src/styles/globals.css",
9 | "baseColor": "zinc",
10 | "cssVariables": true,
11 | "prefix": ""
12 | },
13 | "aliases": {
14 | "components": "@/components",
15 | "utils": "@/lib/utils"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | reactStrictMode: false,
3 | output: 'standalone',
4 | webpack: (config, { isServer }) => {
5 | if (!isServer) {
6 | config.resolve.fallback = {
7 | ...config.resolve.fallback,
8 | fs: false,
9 | };
10 | }
11 | return config;
12 | },
13 | images: {
14 | remotePatterns: [
15 | {
16 | protocol: 'https',
17 | hostname: 'github.com',
18 | },
19 | {
20 | protocol: 'https',
21 | hostname: 'lh3.googleusercontent.com',
22 | },
23 | ],
24 | },
25 | };
26 |
27 | // Injected content via Sentry wizard below
28 |
29 | const { withSentryConfig } = require('@sentry/nextjs');
30 |
31 | module.exports = withSentryConfig(module.exports, {
32 | // For all available options, see:
33 | // https://www.npmjs.com/package/@sentry/webpack-plugin#options
34 |
35 | org: 'sciphi',
36 | project: 'r2r-dashboard',
37 |
38 | // Only print logs for uploading source maps in CI
39 | silent: !process.env.CI,
40 |
41 | // For all available options, see:
42 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
43 |
44 | // Upload a larger set of source maps for prettier stack traces (increases build time)
45 | widenClientFileUpload: true,
46 |
47 | // Automatically annotate React components to show their full name in breadcrumbs and session replay
48 | reactComponentAnnotation: {
49 | enabled: true,
50 | },
51 |
52 | // Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
53 | // This can increase your server load as well as your hosting bill.
54 | // Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
55 | // side errors will fail.
56 | tunnelRoute: '/monitoring',
57 |
58 | // Automatically tree-shake Sentry logger statements to reduce bundle size
59 | disableLogger: true,
60 |
61 | // Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
62 | // See the following for more information:
63 | // https://docs.sentry.io/product/crons/
64 | // https://vercel.com/docs/cron-jobs
65 | automaticVercelMonitors: true,
66 | });
67 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "r2r-dashboard",
3 | "version": "1.0.4",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev -p 3005",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "eslint --fix .",
10 | "lint:verbose": "eslint --config .eslintrc.verbose.json .",
11 | "format": "prettier --write .",
12 | "format:check": "prettier --check ."
13 | },
14 | "husky": {
15 | "hooks": {
16 | "pre-commit": "lint-staged"
17 | }
18 | },
19 | "lint-staged": {
20 | "*.{js,jsx,ts,tsx}": [
21 | "eslint --fix",
22 | "prettier --write"
23 | ],
24 | "*.{json,css,scss,md}": [
25 | "prettier --write"
26 | ]
27 | },
28 | "license": "MIT",
29 | "dependencies": {
30 | "@radix-ui/react-accordion": "^1.2.2",
31 | "@radix-ui/react-alert-dialog": "^1.1.5",
32 | "@radix-ui/react-avatar": "^1.1.2",
33 | "@radix-ui/react-checkbox": "^1.1.3",
34 | "@radix-ui/react-collapsible": "^1.1.2",
35 | "@radix-ui/react-dialog": "^1.1.5",
36 | "@radix-ui/react-dropdown-menu": "^2.1.5",
37 | "@radix-ui/react-icons": "^1.3.2",
38 | "@radix-ui/react-label": "^2.1.1",
39 | "@radix-ui/react-menubar": "^1.1.5",
40 | "@radix-ui/react-popover": "^1.1.5",
41 | "@radix-ui/react-progress": "^1.1.1",
42 | "@radix-ui/react-select": "^2.1.5",
43 | "@radix-ui/react-slider": "^1.2.2",
44 | "@radix-ui/react-slot": "^1.1.1",
45 | "@radix-ui/react-switch": "^1.1.2",
46 | "@radix-ui/react-tabs": "^1.1.2",
47 | "@radix-ui/react-toast": "^1.2.5",
48 | "@radix-ui/react-tooltip": "^1.1.7",
49 | "@react-spring/web": "^9.7.5",
50 | "@sentry/nextjs": "^9",
51 | "@supabase/supabase-js": "^2.48.1",
52 | "@tailwindcss/forms": "^0.5.10",
53 | "@types/topojson-specification": "^1.0.5",
54 | "@visx/axis": "^3.12.0",
55 | "@visx/curve": "^3.12.0",
56 | "@visx/event": "^3.12.0",
57 | "@visx/geo": "^3.12.0",
58 | "@visx/gradient": "^3.12.0",
59 | "@visx/grid": "^3.12.0",
60 | "@visx/group": "^3.12.0",
61 | "@visx/pattern": "^3.12.0",
62 | "@visx/scale": "^3.12.0",
63 | "@visx/shape": "^3.12.0",
64 | "@visx/stats": "^3.12.0",
65 | "@visx/tooltip": "^3.12.0",
66 | "@visx/vendor": "^3.12.0",
67 | "ansi_up": "^6.0.2",
68 | "chart.js": "^4.4.7",
69 | "class-variance-authority": "^0.7.1",
70 | "clsx": "^2.1.1",
71 | "d3": "^7.9.0",
72 | "d3-array": "^3.2.4",
73 | "date-fns": "^3.6.0",
74 | "embla-carousel-react": "^8.5.2",
75 | "framer-motion": "^11.18.2",
76 | "lucide-react": "^0.395.0",
77 | "next": "14.2.5",
78 | "next-themes": "^0.3.0",
79 | "posthog-js": "^1.212.1",
80 | "r2r-js": "^0.4.34",
81 | "react": "18.3.1",
82 | "react-chartjs-2": "^5.3.0",
83 | "react-dom": "18.3.1",
84 | "react-dropzone": "^14.3.5",
85 | "react-markdown": "^9.0.3",
86 | "sass": "^1.83.4",
87 | "tailwind-merge": "^2.6.0",
88 | "tailwindcss-animate": "^1.0.7",
89 | "topojson-client": "^3.1.0",
90 | "uuid": "^10.0.0"
91 | },
92 | "devDependencies": {
93 | "@types/d3": "^7.4.3",
94 | "@types/d3-array": "^3.2.1",
95 | "@types/react": "18.3.3",
96 | "@types/topojson-client": "^3.1.5",
97 | "@types/uuid": "^9.0.8",
98 | "@typescript-eslint/eslint-plugin": "^7.18.0",
99 | "@typescript-eslint/parser": "^7.18.0",
100 | "autoprefixer": "^10.4.20",
101 | "eslint": "^8.57.1",
102 | "eslint-config-next": "14.2.4",
103 | "eslint-config-prettier": "^9.1.0",
104 | "eslint-plugin-import": "^2.31.0",
105 | "eslint-plugin-prettier": "^5.2.3",
106 | "husky": "^9.1.7",
107 | "lint-staged": "^15.4.3",
108 | "prettier": "^3.4.2",
109 | "tailwindcss": "^3.4.17",
110 | "typescript": "5.5.2"
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/public/env-config.js:
--------------------------------------------------------------------------------
1 | window.__RUNTIME_CONFIG__ = {
2 | NEXT_PUBLIC_R2R_DEPLOYMENT_URL: '__NEXT_PUBLIC_R2R_DEPLOYMENT_URL__',
3 | R2R_DASHBOARD_DISABLE_TELEMETRY: '__R2R_DASHBOARD_DISABLE_TELEMETRY__',
4 | SUPABASE_URL: '__SUPABASE_URL__',
5 | SUPABASE_ANON_KEY: '__SUPABASE_ANON_KEY__',
6 | NEXT_PUBLIC_HATCHET_DASHBOARD_URL: '__NEXT_PUBLIC_HATCHET_DASHBOARD_URL__',
7 | NEXT_PUBLIC_SENTRY_DSN: '__NEXT_PUBLIC_SENTRY_DSN__',
8 | };
9 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciPhi-AI/R2R-Application/c4612ab48af002d4cd078659d11db88ed61d53b8/public/favicon.ico
--------------------------------------------------------------------------------
/public/images/default_profile.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciPhi-AI/R2R-Application/c4612ab48af002d4cd078659d11db88ed61d53b8/public/images/favicon.ico
--------------------------------------------------------------------------------
/public/images/github-mark.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/google-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/hatchet-logo.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/public/images/javascript-logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/sciphi.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/sentry.client.config.ts:
--------------------------------------------------------------------------------
1 | // This file configures the initialization of Sentry on the client.
2 | // The config you add here will be used whenever a users loads a page in their browser.
3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
4 |
5 | import * as Sentry from '@sentry/nextjs';
6 |
7 | Sentry.init({
8 | dsn: process.env.NEXT_PUBLIC_SENTRY_DSN || '',
9 |
10 | // Add optional integrations for additional features
11 | integrations: [Sentry.replayIntegration()],
12 |
13 | // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
14 | tracesSampleRate: 1,
15 |
16 | // Define how likely Replay events are sampled.
17 | // This sets the sample rate to be 10%. You may want this to be 100% while
18 | // in development and sample at a lower rate in production
19 | replaysSessionSampleRate: 0.1,
20 |
21 | // Define how likely Replay events are sampled when an error occurs.
22 | replaysOnErrorSampleRate: 1.0,
23 |
24 | // Setting this option to true will print useful information to the console while you're setting up Sentry.
25 | debug: false,
26 | });
27 |
--------------------------------------------------------------------------------
/sentry.edge.config.ts:
--------------------------------------------------------------------------------
1 | // This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
2 | // The config you add here will be used whenever one of the edge features is loaded.
3 | // Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
4 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
5 |
6 | import * as Sentry from '@sentry/nextjs';
7 |
8 | Sentry.init({
9 | dsn: process.env.NEXT_PUBLIC_SENTRY_DSN || '',
10 |
11 | // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
12 | tracesSampleRate: 1,
13 |
14 | // Setting this option to true will print useful information to the console while you're setting up Sentry.
15 | debug: false,
16 | });
17 |
--------------------------------------------------------------------------------
/sentry.server.config.ts:
--------------------------------------------------------------------------------
1 | // This file configures the initialization of Sentry on the server.
2 | // The config you add here will be used whenever the server handles a request.
3 | // https://docs.sentry.io/platforms/javascript/guides/nextjs/
4 |
5 | import * as Sentry from '@sentry/nextjs';
6 |
7 | Sentry.init({
8 | dsn: process.env.NEXT_PUBLIC_SENTRY_DSN || '',
9 |
10 | // Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
11 | tracesSampleRate: 1,
12 |
13 | // Setting this option to true will print useful information to the console while you're setting up Sentry.
14 | debug: false,
15 | });
16 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/DefaultQueries.tsx:
--------------------------------------------------------------------------------
1 | import { Lightbulb, FlaskConical, Flame, Earth } from 'lucide-react';
2 | import { FC } from 'react';
3 |
4 | import { Logo } from '@/components/shared/Logo';
5 | import { Alert, AlertDescription } from '@/components/ui/alert';
6 | import { DefaultQueriesProps } from '@/types';
7 |
8 | export const DefaultQueries: FC = ({ setQuery, mode }) => {
9 | const defaultRagQueries = [
10 | {
11 | query: 'What is the main topic of the uploaded documents?',
12 | icon: ,
13 | },
14 | {
15 | query: 'Summarize key points for me.',
16 | icon: ,
17 | },
18 | {
19 | query: 'What issues do you see with the documents?',
20 | icon: ,
21 | },
22 | {
23 | query: 'How are these documents interrelated?',
24 | icon: ,
25 | },
26 | ];
27 |
28 | const defaultAgentQueries = [
29 | {
30 | query: 'Hey! How are you today?',
31 | icon: ,
32 | },
33 | {
34 | query: 'Can you help me understand my documents better?',
35 | icon: ,
36 | },
37 | {
38 | query: 'How might agentic RAG help me in the long run?',
39 | icon: ,
40 | },
41 | {
42 | query: 'What is the coolest thing you can do?',
43 | icon: ,
44 | },
45 | ];
46 |
47 | const getQueriesBasedOnMode = (mode: 'rag' | 'rag_agent') => {
48 | if (mode === 'rag') {
49 | return defaultRagQueries;
50 | } else if (mode === 'rag_agent') {
51 | return defaultAgentQueries;
52 | } else {
53 | throw new Error('Invalid mode');
54 | }
55 | };
56 |
57 | const defaultQueries = getQueriesBasedOnMode(mode);
58 |
59 | return (
60 |
61 |
62 |
63 | {defaultQueries.map(({ query, icon }, index) => (
64 |
setQuery(query)}
68 | >
69 | {icon}
70 |
71 | {query}
72 |
73 |
74 | ))}
75 |
76 |
77 | );
78 | };
79 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/DownloadFileContainer.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { FileDown } from 'lucide-react';
3 | import React, { useState } from 'react';
4 |
5 | import { Spinner } from '@/components/Spinner';
6 | import { Button } from '@/components/ui/Button';
7 | import { useUserContext } from '@/context/UserContext';
8 |
9 | interface DownloadFileContainerProps {
10 | id: string;
11 | fileName: string;
12 | showToast: (message: {
13 | title: string;
14 | description: string;
15 | variant: 'default' | 'destructive' | 'success';
16 | }) => void;
17 | }
18 |
19 | const DownloadButtonContainer: React.FC = ({
20 | id,
21 | fileName,
22 | showToast,
23 | }) => {
24 | const [isDownloading, setIsDownloading] = useState(false);
25 | const { getClient } = useUserContext();
26 |
27 | const handleDocumentDownload = async () => {
28 | setIsDownloading(true);
29 |
30 | try {
31 | const client = await getClient();
32 | if (!client) {
33 | throw new Error('Failed to get authenticated client');
34 | }
35 |
36 | const blob = await client.documents.download({ id: id });
37 |
38 | const url = window.URL.createObjectURL(blob);
39 |
40 | const a = document.createElement('a');
41 | a.style.display = 'none';
42 | a.href = url;
43 | a.download = fileName;
44 |
45 | document.body.appendChild(a);
46 | a.click();
47 |
48 | window.URL.revokeObjectURL(url);
49 | document.body.removeChild(a);
50 |
51 | showToast({
52 | variant: 'success',
53 | title: 'Download Successful',
54 | description: 'The file has been downloaded successfully.',
55 | });
56 | } catch (error: any) {
57 | console.error('Error downloading file:', error);
58 | showToast({
59 | variant: 'destructive',
60 | title: 'Download Failed',
61 | description: error.message || 'An unknown error occurred',
62 | });
63 | } finally {
64 | setIsDownloading(false);
65 | }
66 | };
67 |
68 | return (
69 |
70 |
83 |
84 | );
85 | };
86 |
87 | export default DownloadButtonContainer;
88 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/ExtractContainer.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { FileOutput } from 'lucide-react';
3 | import React, { useState } from 'react';
4 |
5 | import { Spinner } from '@/components/Spinner';
6 | import { Button } from '@/components/ui/Button';
7 | import { useUserContext } from '@/context/UserContext';
8 |
9 | interface ExtractContainerProps {
10 | id: string;
11 | ingestionStatus: string;
12 | showToast: (message: {
13 | title: string;
14 | description: string;
15 | variant: 'default' | 'destructive' | 'success';
16 | }) => void;
17 | }
18 |
19 | const ExtractButtonContainer: React.FC = ({
20 | id,
21 | ingestionStatus,
22 | showToast,
23 | }) => {
24 | const [isExtracting, setIsExtracting] = useState(false);
25 | const { getClient } = useUserContext();
26 |
27 | const isIngestionValid = () => {
28 | const status = ingestionStatus.toUpperCase();
29 | return status === 'SUCCESS';
30 | };
31 |
32 | const handleDocumentExtraction = async () => {
33 | setIsExtracting(true);
34 |
35 | try {
36 | const client = await getClient();
37 | if (!client) {
38 | throw new Error('Failed to get authenticated client');
39 | }
40 |
41 | client.documents.extract({ id: id });
42 |
43 | showToast({
44 | variant: 'success',
45 | title: 'Extraction Started',
46 | description:
47 | 'The extraction request has been sent and will be processed in the background.',
48 | });
49 | } catch (error: any) {
50 | console.error('Error initiating extraction:', error);
51 | showToast({
52 | variant: 'destructive',
53 | title: 'Extraction Failed',
54 | description: error.message || 'An unknown error occurred',
55 | });
56 | } finally {
57 | setIsExtracting(false);
58 | }
59 | };
60 |
61 | return (
62 |
63 |
76 |
77 | );
78 | };
79 | export default ExtractButtonContainer;
80 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/MessageBubble.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Message } from '@/types';
4 |
5 | const MessageBubble: React.FC<{ message: Message; isStreaming?: boolean }> = ({
6 | message,
7 | isStreaming,
8 | }) => {
9 | if (message.role === 'user') {
10 | return (
11 |
12 |
13 |
{message.content}
14 |
15 |
16 | );
17 | } else if (message.role === 'assistant') {
18 | return (
19 |
20 |
23 |
24 | {message.content}
25 | {message.isStreaming && (
26 | ▋
27 | )}
28 |
29 |
30 |
31 | );
32 | }
33 | return null;
34 | };
35 |
36 | export default MessageBubble;
37 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/PipelineStatus.tsx:
--------------------------------------------------------------------------------
1 | import { ServerStats } from 'r2r-js';
2 | import React, { useState, useEffect, useCallback } from 'react';
3 |
4 | import { useUserContext } from '@/context/UserContext';
5 | import { PipelineStatusProps } from '@/types';
6 |
7 | export function useConnectionStatus(
8 | deploymentUrl?: string,
9 | onStatusChange?: (isConnected: boolean) => void
10 | ) {
11 | const { getClient } = useUserContext();
12 | const [isConnected, setIsConnected] = useState(false);
13 | const [serverStats, setServerStats] = useState(null);
14 | const [localUptime, setLocalUptime] = useState(0);
15 |
16 | const checkStatusAndFetchStats = useCallback(async () => {
17 | if (!deploymentUrl) {
18 | setIsConnected(false);
19 | onStatusChange?.(false);
20 | return;
21 | }
22 |
23 | const client = getClient();
24 | if (!client) {
25 | setIsConnected(false);
26 | onStatusChange?.(false);
27 | return;
28 | }
29 |
30 | try {
31 | await client.system.health();
32 | setIsConnected(true);
33 | onStatusChange?.(true);
34 |
35 | const stats = await client.system.status();
36 | setServerStats(stats.results);
37 | setLocalUptime(stats.results.uptimeSeconds);
38 | } catch (error) {
39 | console.error('Error checking status or fetching stats:', error);
40 | setIsConnected(false);
41 | onStatusChange?.(false);
42 | }
43 | }, [deploymentUrl, getClient, onStatusChange]);
44 |
45 | useEffect(() => {
46 | checkStatusAndFetchStats();
47 | const intervalId = setInterval(checkStatusAndFetchStats, 60000); // Check every minute
48 |
49 | return () => clearInterval(intervalId);
50 | }, [checkStatusAndFetchStats]);
51 |
52 | useEffect(() => {
53 | const uptimeIntervalId = setInterval(() => {
54 | setLocalUptime((prevUptime) => prevUptime + 1);
55 | }, 1000);
56 |
57 | return () => clearInterval(uptimeIntervalId);
58 | }, []);
59 |
60 | return { isConnected, serverStats, localUptime };
61 | }
62 |
63 | export function PipelineStatus({
64 | className = '',
65 | onStatusChange,
66 | }: PipelineStatusProps) {
67 | const { pipeline } = useUserContext();
68 | const { isConnected, localUptime } = useConnectionStatus(
69 | pipeline?.deploymentUrl,
70 | onStatusChange
71 | );
72 |
73 | return (
74 |
75 |
78 |
79 | Status: {isConnected ? 'Connected' : 'No Connection'}
80 |
81 |
82 | );
83 | }
84 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/ServerCard.tsx:
--------------------------------------------------------------------------------
1 | import { Link, Check, ClipboardCheck } from 'lucide-react';
2 | import React, { useState } from 'react';
3 |
4 | import {
5 | PipelineStatus,
6 | useConnectionStatus,
7 | } from '@/components/ChatDemo/PipelineStatus';
8 | import { formatUptime } from '@/components/ChatDemo/utils/formatUptime';
9 | import {
10 | Card,
11 | CardHeader,
12 | CardTitle,
13 | CardDescription,
14 | CardContent,
15 | } from '@/components/ui/card';
16 | import { brandingConfig } from '@/config/brandingConfig';
17 | import { R2RServerCardProps } from '@/types';
18 |
19 | const R2RServerCard: React.FC = ({
20 | pipeline,
21 | onStatusChange,
22 | }) => {
23 | const [copied, setCopied] = useState(false);
24 | const { isConnected, serverStats, localUptime } = useConnectionStatus(
25 | pipeline?.deploymentUrl,
26 | onStatusChange
27 | );
28 |
29 | const handleCopy = (text: string) => {
30 | navigator.clipboard.writeText(text).then(
31 | () => {
32 | setCopied(true);
33 | setTimeout(() => setCopied(false), 2000);
34 | },
35 | (err) => console.error('Failed to copy text: ', err)
36 | );
37 | };
38 |
39 | return (
40 |
41 |
42 |
43 |
44 | {brandingConfig.deploymentName} Server
45 |
46 |
47 |
48 |
49 | Your {brandingConfig.deploymentName} server deployment.
50 |
51 |
52 |
53 |
54 |
55 |
Uptime:
56 |
{isConnected ? formatUptime(localUptime) : 'N/A'}
57 |
58 |
59 |
CPU Usage:
60 |
{serverStats ? `${serverStats.cpuUsage.toFixed(1)}%` : 'N/A'}
61 |
62 |
63 |
Start Time:
64 |
65 | {serverStats
66 | ? new Date(serverStats.startTime).toLocaleString()
67 | : 'N/A'}
68 |
69 |
70 |
71 |
Memory Usage:
72 |
73 | {serverStats ? `${serverStats.memoryUsage.toFixed(1)}%` : 'N/A'}
74 |
75 |
76 |
77 |
78 |
79 |
80 | {pipeline?.deploymentUrl}
81 |
82 | {copied ? (
83 |
84 | ) : (
85 | handleCopy(pipeline?.deploymentUrl || '')}
88 | />
89 | )}
90 |
91 |
92 |
93 | );
94 | };
95 |
96 | export default R2RServerCard;
97 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/SingleSwitch.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 |
3 | import { Switch } from '@/components/ui/switch';
4 | import {
5 | Tooltip,
6 | TooltipContent,
7 | TooltipProvider,
8 | TooltipTrigger,
9 | } from '@/components/ui/tooltip';
10 | import { SingleSwitchProps } from '@/types';
11 |
12 | const SingleSwitch: React.FC = ({
13 | id,
14 | initialChecked,
15 | onChange,
16 | label,
17 | tooltipText,
18 | }) => {
19 | const [isChecked, setIsChecked] = useState(initialChecked);
20 |
21 | useEffect(() => {
22 | setIsChecked(initialChecked);
23 | }, [initialChecked]);
24 |
25 | const handleSwitchChange = (checked: boolean) => {
26 | setIsChecked(checked);
27 | onChange(id, checked);
28 | };
29 |
30 | return (
31 |
32 | {label && (
33 |
36 | )}
37 |
38 |
39 |
40 |
45 |
46 |
47 | {tooltipText}
48 |
49 |
50 |
51 |
52 | );
53 | };
54 |
55 | export default SingleSwitch;
56 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/SwitchManager.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useCallback } from 'react';
2 |
3 | import { Switch } from '@/types';
4 |
5 | const useSwitchManager = () => {
6 | const [switches, setSwitches] = useState>({});
7 |
8 | const initializeSwitch = useCallback(
9 | (
10 | id: string,
11 | initialChecked: boolean,
12 | label: string,
13 | tooltipText: string
14 | ) => {
15 | setSwitches((prevSwitches) => ({
16 | ...prevSwitches,
17 | [id]: { checked: initialChecked, label, tooltipText },
18 | }));
19 | },
20 | []
21 | );
22 |
23 | const updateSwitch = useCallback((id: string, checked: boolean) => {
24 | setSwitches((prevSwitches) => ({
25 | ...prevSwitches,
26 | [id]: { ...prevSwitches[id], checked },
27 | }));
28 | }, []);
29 |
30 | return {
31 | switches,
32 | initializeSwitch,
33 | updateSwitch,
34 | };
35 | };
36 |
37 | export default useSwitchManager;
38 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/UpdateButtonContainer.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { FileUp } from 'lucide-react';
3 | import React, { useState, useRef } from 'react';
4 |
5 | import { Spinner } from '@/components/Spinner';
6 | import { Button } from '@/components/ui/Button';
7 | import { useUserContext } from '@/context/UserContext';
8 | import { UpdateButtonContainerProps } from '@/types';
9 |
10 | const UpdateButtonContainer: React.FC = ({
11 | id,
12 | onUpdateSuccess,
13 | showToast,
14 | }) => {
15 | const [isUpdating, setIsUpdating] = useState(false);
16 | const fileInputRef = useRef(null);
17 | const { getClient } = useUserContext();
18 |
19 | const handleDocumentUpdate = async (
20 | event: React.ChangeEvent
21 | ) => {
22 | event.preventDefault();
23 | if (
24 | fileInputRef.current &&
25 | fileInputRef.current.files &&
26 | fileInputRef.current.files.length
27 | ) {
28 | setIsUpdating(true);
29 | const file = fileInputRef.current.files[0];
30 |
31 | try {
32 | const client = await getClient();
33 | if (!client) {
34 | throw new Error('Failed to get authenticated client');
35 | }
36 |
37 | const metadata = { title: file.name };
38 |
39 | await client.documents.create({
40 | id: id,
41 | file: file,
42 | metadata: [metadata],
43 | });
44 |
45 | showToast({
46 | variant: 'success',
47 | title: 'Upload Successful',
48 | description: 'All files have been uploaded successfully.',
49 | });
50 | onUpdateSuccess();
51 | } catch (error: any) {
52 | console.error('Error updating file:', error);
53 | console.error('Error details:', error.response?.data);
54 | showToast({
55 | variant: 'destructive',
56 | title: 'Upload Failed',
57 | description: error.message || 'An unknown error occurred',
58 | });
59 | } finally {
60 | setIsUpdating(false);
61 | if (fileInputRef.current) {
62 | fileInputRef.current.value = '';
63 | }
64 | }
65 | }
66 | };
67 |
68 | const handleUpdateButtonClick = () => {
69 | if (fileInputRef.current) {
70 | fileInputRef.current.click();
71 | }
72 | };
73 |
74 | return (
75 |
76 |
89 |
95 |
96 | );
97 | };
98 |
99 | export default UpdateButtonContainer;
100 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/UploadDialog.tsx:
--------------------------------------------------------------------------------
1 | import { Upload, X } from 'lucide-react';
2 | import React, { useCallback, useState } from 'react';
3 | import { useDropzone } from 'react-dropzone';
4 |
5 | import { Button } from '@/components/ui/Button';
6 | import {
7 | Dialog,
8 | DialogContent,
9 | DialogHeader,
10 | DialogTitle,
11 | } from '@/components/ui/dialog';
12 | import { UploadDialogProps } from '@/types';
13 |
14 | export const UploadDialog: React.FC = ({
15 | isOpen,
16 | onClose,
17 | onUpload,
18 | }) => {
19 | const [files, setFiles] = useState([]);
20 |
21 | const onDrop = useCallback((acceptedFiles: File[]) => {
22 | setFiles((prevFiles) => {
23 | const newFiles = acceptedFiles.filter((newFile) => {
24 | const isDuplicate = prevFiles.some(
25 | (existingFile) =>
26 | existingFile.name === newFile.name &&
27 | existingFile.size === newFile.size
28 | );
29 | return !isDuplicate;
30 | });
31 | return [...prevFiles, ...newFiles];
32 | });
33 | }, []);
34 |
35 | const { getRootProps, getInputProps, isDragActive } = useDropzone({
36 | onDrop,
37 | multiple: true,
38 | });
39 |
40 | const handleUpload = () => {
41 | onUpload(files);
42 | setFiles([]);
43 | onClose();
44 | };
45 |
46 | const removeFile = (index: number) => {
47 | setFiles((prevFiles) => prevFiles.filter((_, i) => i !== index));
48 | };
49 |
50 | return (
51 |
105 | );
106 | };
107 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/createFile.tsx:
--------------------------------------------------------------------------------
1 | import { FileUp } from 'lucide-react';
2 | import React, { useState, Dispatch, SetStateAction } from 'react';
3 |
4 | import { Button } from '@/components/ui/Button';
5 | import { useUserContext } from '@/context/UserContext';
6 | import { generateIdFromLabel } from '@/lib/utils';
7 |
8 | import { UploadDialog } from './UploadDialog';
9 |
10 | export interface CreateFileButton {
11 | userId: string | null;
12 | onUploadSuccess?: () => Promise;
13 | showToast?: (message: {
14 | title: string;
15 | description: string;
16 | variant: 'default' | 'destructive' | 'success';
17 | }) => void;
18 | setCurrentPage?: React.Dispatch>;
19 | documentsPerPage?: number;
20 | }
21 |
22 | export const CreateFileButton: React.FC = ({
23 | userId,
24 | onUploadSuccess,
25 | showToast = () => {},
26 | setCurrentPage,
27 | documentsPerPage,
28 | }) => {
29 | const [isUploading, setIsUploading] = useState(false);
30 | const [isDialogOpen, setIsDialogOpen] = useState(false);
31 | const { getClient } = useUserContext();
32 |
33 | const handleCreateDocument = async (files: File[]) => {
34 | setIsUploading(true);
35 | const client = await getClient();
36 | if (!client) {
37 | throw new Error('Failed to get authenticated client');
38 | }
39 |
40 | try {
41 | const uploadedFiles: any[] = [];
42 | const metadatas: Record[] = [];
43 | const userIds: (string | null)[] = [];
44 |
45 | for (const file of files) {
46 | const fileId = generateIdFromLabel(file.name);
47 | uploadedFiles.push({ documentId: fileId, title: file.name });
48 | metadatas.push({ title: file.name });
49 | userIds.push(userId);
50 | }
51 |
52 | for (const file of files) {
53 | await client.documents.create({
54 | file: file,
55 | });
56 | }
57 |
58 | showToast({
59 | variant: 'success',
60 | title: 'Upload Successful',
61 | description: 'All files have been uploaded successfully.',
62 | });
63 |
64 | if (onUploadSuccess) {
65 | const updatedDocuments = await onUploadSuccess();
66 | if (updatedDocuments.length > 0 && setCurrentPage && documentsPerPage) {
67 | const totalPages = Math.ceil(
68 | updatedDocuments.length / documentsPerPage
69 | );
70 | setCurrentPage(1);
71 | } else if (setCurrentPage) {
72 | setCurrentPage(1);
73 | }
74 | }
75 | } catch (error: any) {
76 | console.error('Error uploading files:', error);
77 | showToast({
78 | variant: 'destructive',
79 | title: 'Upload Failed',
80 | description: error.message,
81 | });
82 | } finally {
83 | setIsUploading(false);
84 | }
85 | };
86 |
87 | return (
88 | <>
89 |
101 | setIsDialogOpen(false)}
104 | onUpload={handleCreateDocument}
105 | />
106 | >
107 | );
108 | };
109 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/popover.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as PopoverPrimitive from '@radix-ui/react-popover';
4 | import * as React from 'react';
5 |
6 | import { cn } from './utils/cn';
7 |
8 | const Popover = PopoverPrimitive.Root;
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger;
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
16 |
17 |
27 |
28 | ));
29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName;
30 |
31 | export { Popover, PopoverTrigger, PopoverContent };
32 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/remove.tsx:
--------------------------------------------------------------------------------
1 | import { FileMinus, UserMinus, Minus } from 'lucide-react';
2 | import React from 'react';
3 |
4 | import {
5 | AlertDialog,
6 | AlertDialogAction,
7 | AlertDialogCancel,
8 | AlertDialogContent,
9 | AlertDialogDescription,
10 | AlertDialogFooter,
11 | AlertDialogHeader,
12 | AlertDialogTitle,
13 | AlertDialogTrigger,
14 | } from '@/components/ui/alert-dialog';
15 | import { Button } from '@/components/ui/Button';
16 | import { useUserContext } from '@/context/UserContext';
17 |
18 | export interface RemoveButtonProps {
19 | itemId: string;
20 | collectionId: string;
21 | itemType: 'document' | 'user' | 'entity' | 'relationship' | 'community';
22 | onSuccess: () => void;
23 | showToast: (message: {
24 | title: string;
25 | description: string;
26 | variant: 'default' | 'destructive' | 'success';
27 | }) => void;
28 | }
29 |
30 | export const RemoveButton: React.FC = ({
31 | itemId,
32 | collectionId,
33 | itemType,
34 | onSuccess,
35 | showToast,
36 | }) => {
37 | const { getClient } = useUserContext();
38 |
39 | const handleRemove = async () => {
40 | try {
41 | const client = await getClient();
42 | if (!client) {
43 | throw new Error('Failed to get authenticated client');
44 | }
45 |
46 | if (itemType === 'document') {
47 | await client.collections.removeDocument({
48 | id: collectionId,
49 | documentId: itemId,
50 | });
51 | } else if (itemType === 'user') {
52 | await client.collections.removeUser({
53 | id: collectionId,
54 | userId: itemId,
55 | });
56 | } else if (itemType === 'entity') {
57 | await client.graphs.removeEntity({
58 | collectionId: collectionId,
59 | entityId: itemId,
60 | });
61 | } else if (itemType === 'relationship') {
62 | await client.graphs.removeRelationship({
63 | collectionId: collectionId,
64 | relationshipId: itemId,
65 | });
66 | } else if (itemType === 'community') {
67 | await client.graphs.deleteCommunity({
68 | collectionId: collectionId,
69 | communityId: itemId,
70 | });
71 | }
72 |
73 | showToast({
74 | variant: 'success',
75 | title: `${itemType.charAt(0).toUpperCase() + itemType.slice(1)} removed`,
76 | description: `The ${itemType} has been successfully removed from the collection`,
77 | });
78 | onSuccess();
79 | } catch (error: unknown) {
80 | console.error(`Error removing ${itemType}:`, error);
81 | if (error instanceof Error) {
82 | showToast({
83 | variant: 'destructive',
84 | title: `Failed to remove ${itemType}`,
85 | description: error.message,
86 | });
87 | } else {
88 | showToast({
89 | variant: 'destructive',
90 | title: `Failed to remove ${itemType}`,
91 | description: 'An unknown error occurred',
92 | });
93 | }
94 | }
95 | };
96 |
97 | const Icon =
98 | itemType === 'document'
99 | ? FileMinus
100 | : itemType === 'user'
101 | ? UserMinus
102 | : Minus;
103 |
104 | return (
105 |
106 |
107 |
114 |
115 |
116 |
117 |
118 | Are you sure you want to remove this {itemType} from the collection?
119 |
120 |
121 | This action will remove the {itemType} from the current collection.
122 | The {itemType} will not be deleted from the system.
123 |
124 |
125 |
126 | Cancel
127 | Remove
128 |
129 |
130 |
131 | );
132 | };
133 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/search.tsx:
--------------------------------------------------------------------------------
1 | import { ArrowRight } from 'lucide-react';
2 | import { useRouter } from 'next/navigation';
3 | import React, { FC, useState, useCallback } from 'react';
4 |
5 | import { Button } from '@/components/ui/Button';
6 | import { SearchProps } from '@/types';
7 |
8 | function debounce void>(
9 | func: T,
10 | wait: number
11 | ): (...args: Parameters) => void {
12 | let timeout: NodeJS.Timeout | null = null;
13 | return function (this: ThisParameterType, ...args: Parameters) {
14 | if (timeout) {
15 | clearTimeout(timeout);
16 | }
17 | timeout = setTimeout(() => func.apply(this, args), wait);
18 | };
19 | }
20 |
21 | export const Search: FC = ({
22 | pipeline,
23 | setQuery,
24 | placeholder,
25 | disabled = false,
26 | }) => {
27 | const [value, setValue] = useState('');
28 | const router = useRouter();
29 |
30 | if (!placeholder) {
31 | placeholder = 'Search over your documents…';
32 | }
33 |
34 | const navigateToSearch = useCallback(
35 | debounce((searchValue: string) => {
36 | if (pipeline) {
37 | router.push(`/chat/?q=${encodeURIComponent(searchValue)}`);
38 | }
39 | }, 50),
40 | [router, pipeline]
41 | );
42 |
43 | const handleSubmit = (e: React.FormEvent) => {
44 | e.preventDefault();
45 | if (value.trim()) {
46 | navigateToSearch(value.trim());
47 | setQuery(value.trim());
48 | setValue('');
49 | }
50 | };
51 |
52 | return (
53 |
76 | );
77 | };
78 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { HTMLAttributes } from 'react';
2 |
3 | import { cn } from './utils/cn';
4 |
5 | function Skeleton({ className, ...props }: HTMLAttributes) {
6 | return (
7 |
11 | );
12 | }
13 |
14 | export { Skeleton };
15 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/title.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import { useRouter } from 'next/navigation';
3 |
4 | import { getSearchUrl } from './utils/get-search-url';
5 |
6 | export const Title = ({
7 | query,
8 | userId,
9 | model,
10 | setModel,
11 | }: {
12 | query: string;
13 | userId: string;
14 | model: string;
15 | setModel: any;
16 | }) => {
17 | const router = useRouter();
18 |
19 | return (
20 |
21 |
22 |
25 |
29 | {query}
30 |
31 |
32 |
33 |
34 |
35 | {/* */}
44 |
53 |
54 |
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/update.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 | import React, { useState, useRef, FormEvent, ChangeEvent } from 'react';
3 |
4 | import { useUserContext } from '@/context/UserContext';
5 | import { UpdateButtonProps } from '@/types';
6 |
7 | export const UpdateButton: React.FC = ({
8 | id,
9 | onUpdateSuccess,
10 | showToast = () => {},
11 | }) => {
12 | const [isUpdating, setIsUpdating] = useState(false);
13 | const fileInputRef = useRef(null);
14 | const { getClient } = useUserContext();
15 |
16 | const handleDocumentUpdate = async (
17 | event: FormEvent | ChangeEvent
18 | ) => {
19 | event.preventDefault();
20 | if (
21 | fileInputRef.current &&
22 | fileInputRef.current.files &&
23 | fileInputRef.current.files.length
24 | ) {
25 | setIsUpdating(true);
26 | const file = fileInputRef.current.files[0];
27 |
28 | try {
29 | const client = await getClient();
30 | if (!client) {
31 | throw new Error('Failed to get authenticated client');
32 | }
33 |
34 | const metadata = { title: file.name };
35 |
36 | // Updating is currently not supported in the SDK
37 |
38 | await client.documents.create({
39 | id: id,
40 | file: file,
41 | metadata: [metadata],
42 | });
43 | showToast({
44 | variant: 'success',
45 | title: 'Upload Successful',
46 | description: 'All files have been uploaded successfully.',
47 | });
48 | onUpdateSuccess();
49 | } catch (error: unknown) {
50 | console.error('Error updating file:', error);
51 | let errorMessage = 'An unknown error occurred';
52 | if (error instanceof Error) {
53 | errorMessage = error.message;
54 | }
55 | showToast({
56 | variant: 'destructive',
57 | title: 'Upload Failed',
58 | description: errorMessage,
59 | });
60 | } finally {
61 | setIsUpdating(false);
62 | if (fileInputRef.current) {
63 | fileInputRef.current.value = '';
64 | }
65 | }
66 | }
67 | };
68 |
69 | const handleUpdateButtonClick = () => {
70 | if (fileInputRef.current) {
71 | fileInputRef.current.click();
72 | }
73 | };
74 |
75 | return (
76 |
77 |
97 |
98 | );
99 | };
100 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/utils/cn.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from 'clsx';
2 | import { twMerge } from 'tailwind-merge';
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/utils/collectionCreationModal.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | import { Button } from '@/components/ui/Button';
4 | import {
5 | Dialog,
6 | DialogContent,
7 | DialogHeader,
8 | DialogTitle,
9 | DialogFooter,
10 | } from '@/components/ui/dialog';
11 | import { Input } from '@/components/ui/input';
12 | import { Textarea } from '@/components/ui/textarea';
13 | import { useToast } from '@/components/ui/use-toast';
14 | import { useUserContext } from '@/context/UserContext';
15 |
16 | interface CollectionCreationModalProps {
17 | open: boolean;
18 | onClose: () => void;
19 | onCollectionCreated: () => void;
20 | }
21 |
22 | const CollectionCreationModal: React.FC = ({
23 | open,
24 | onClose,
25 | onCollectionCreated,
26 | }) => {
27 | const { getClient } = useUserContext();
28 | const { toast } = useToast();
29 |
30 | const [name, setName] = useState('');
31 | const [description, setDescription] = useState('');
32 | const [isCreating, setIsCreating] = useState(false);
33 |
34 | const handleCreate = async () => {
35 | if (!name.trim()) {
36 | toast({
37 | title: 'Validation Error',
38 | description: 'Collection name is required.',
39 | variant: 'destructive',
40 | });
41 | return;
42 | }
43 |
44 | setIsCreating(true);
45 | try {
46 | const client = await getClient();
47 | if (!client) {
48 | throw new Error('Failed to get authenticated client');
49 | }
50 |
51 | await client.collections.create({
52 | name: name.trim(),
53 | description: description.trim() || undefined,
54 | });
55 | toast({
56 | title: 'Collection Created',
57 | description: `Collection "${name}" has been successfully created.`,
58 | variant: 'success',
59 | });
60 | setName('');
61 | setDescription('');
62 | onClose();
63 | onCollectionCreated();
64 | } catch (error: any) {
65 | console.error('Error creating collection:', error);
66 | toast({
67 | title: 'Error',
68 | description:
69 | error?.message ||
70 | 'An unexpected error occurred while creating the collection.',
71 | variant: 'destructive',
72 | });
73 | } finally {
74 | setIsCreating(false);
75 | }
76 | };
77 |
78 | const handleClose = () => {
79 | if (!isCreating) {
80 | setName('');
81 | setDescription('');
82 | onClose();
83 | }
84 | };
85 |
86 | return (
87 |
121 | );
122 | };
123 |
124 | export default CollectionCreationModal;
125 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/utils/containerObjectCreationModal.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | import { Button } from '@/components/ui/Button';
4 | import {
5 | Dialog,
6 | DialogContent,
7 | DialogHeader,
8 | DialogTitle,
9 | DialogFooter,
10 | } from '@/components/ui/dialog';
11 | import { Input } from '@/components/ui/input';
12 | import { Textarea } from '@/components/ui/textarea';
13 | import { useToast } from '@/components/ui/use-toast';
14 | import { useUserContext } from '@/context/UserContext';
15 |
16 | export enum ContainerType {
17 | Collection = 'collection',
18 | Graph = 'graph',
19 | }
20 |
21 | interface containerObjectCreationModalProps {
22 | containerType: ContainerType;
23 | open: boolean;
24 | onClose: () => void;
25 | onCollectionCreated: () => void;
26 | }
27 |
28 | const ContainerObjectCreationModal: React.FC<
29 | containerObjectCreationModalProps
30 | > = ({ containerType, open, onClose, onCollectionCreated }) => {
31 | const { getClient } = useUserContext();
32 | const { toast } = useToast();
33 |
34 | const [name, setName] = useState('');
35 | const [description, setDescription] = useState('');
36 | const [isCreating, setIsCreating] = useState(false);
37 |
38 | const capitalizedType =
39 | containerType.charAt(0).toUpperCase() + containerType.slice(1);
40 |
41 | const handleCreate = async () => {
42 | if (!name.trim()) {
43 | toast({
44 | title: 'Validation Error',
45 | description: `${capitalizedType} name is required.`,
46 | variant: 'destructive',
47 | });
48 | return;
49 | }
50 |
51 | setIsCreating(true);
52 | try {
53 | const client = await getClient();
54 | if (!client) {
55 | throw new Error('Failed to get authenticated client');
56 | }
57 |
58 | const createPayload = {
59 | name: name.trim(),
60 | description: description.trim() || undefined,
61 | };
62 |
63 | if (containerType === ContainerType.Collection) {
64 | await client.collections.create(createPayload);
65 | } else {
66 | console.log('Creating graph:', createPayload);
67 | // await client.graphs.pull(createPayload);
68 | }
69 |
70 | toast({
71 | title: `${capitalizedType} Created`,
72 | description: `${capitalizedType} "${name}" has been successfully created.`,
73 | variant: 'success',
74 | });
75 | setName('');
76 | setDescription('');
77 | onClose();
78 | onCollectionCreated();
79 | } catch (error: any) {
80 | console.error(`Error creating ${containerType}:`, error);
81 | toast({
82 | title: 'Error',
83 | description:
84 | error?.message ||
85 | `An unexpected error occurred while creating the ${containerType}.`,
86 | variant: 'destructive',
87 | });
88 | } finally {
89 | setIsCreating(false);
90 | }
91 | };
92 |
93 | const handleClose = () => {
94 | if (!isCreating) {
95 | setName('');
96 | setDescription('');
97 | onClose();
98 | }
99 | };
100 |
101 | return (
102 |
138 | );
139 | };
140 |
141 | export default ContainerObjectCreationModal;
142 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/utils/documentSorter.ts:
--------------------------------------------------------------------------------
1 | import { DocumentFilterCriteria, DocumentInfoType } from '../../../types';
2 |
3 | export const getFilteredAndSortedDocuments = (
4 | documents: DocumentInfoType[],
5 | filterCriteria: DocumentFilterCriteria
6 | ) => {
7 | return [...documents].sort((a, b) => {
8 | if (filterCriteria.sort === 'title') {
9 | return filterCriteria.order === 'asc'
10 | ? a.title.localeCompare(b.title)
11 | : b.title.localeCompare(a.title);
12 | } else if (filterCriteria.sort === 'date') {
13 | return filterCriteria.order === 'asc'
14 | ? new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime()
15 | : new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();
16 | }
17 | return 0;
18 | });
19 | };
20 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/utils/editPromptDialog.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 |
3 | import { Button } from '@/components/ui/Button';
4 | import {
5 | Dialog,
6 | DialogContent,
7 | DialogHeader,
8 | DialogTitle,
9 | } from '@/components/ui/dialog';
10 | import { Textarea } from '@/components/ui/textarea';
11 | import { useToast } from '@/components/ui/use-toast';
12 | import { useUserContext } from '@/context/UserContext';
13 | import { EditPromptDialogProps } from '@/types';
14 |
15 | const EditPromptDialog: React.FC = ({
16 | open,
17 | onClose,
18 | promptName,
19 | promptTemplate,
20 | onSaveSuccess,
21 | }) => {
22 | const [editedTemplate, setEditedTemplate] = useState(promptTemplate);
23 | const { toast } = useToast();
24 | const { getClient } = useUserContext();
25 |
26 | useEffect(() => {
27 | setEditedTemplate(promptTemplate);
28 | }, [promptTemplate]);
29 |
30 | const handleSave = async () => {
31 | try {
32 | const client = await getClient();
33 | if (!client) {
34 | throw new Error('Failed to get authenticated client');
35 | }
36 |
37 | await client.prompts.update({
38 | name: promptName,
39 | template: editedTemplate,
40 | });
41 | toast({
42 | title: 'Prompt updated',
43 | description: 'The prompt has been successfully updated.',
44 | variant: 'success',
45 | });
46 | onSaveSuccess();
47 | onClose();
48 | } catch (error) {
49 | toast({
50 | title: 'Error',
51 | description: 'Failed to update the prompt. Please try again.',
52 | variant: 'destructive',
53 | });
54 | console.error('Error updating prompt:', error);
55 | }
56 | };
57 |
58 | return (
59 |
77 | );
78 | };
79 |
80 | export default EditPromptDialog;
81 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/utils/formatUptime.ts:
--------------------------------------------------------------------------------
1 | export function formatUptime(seconds: number): string {
2 | if (seconds < 60) {
3 | return `${Math.floor(seconds)} seconds`;
4 | } else if (seconds < 3600) {
5 | const minutes = Math.floor(seconds / 60);
6 | const remainingSeconds = Math.floor(seconds % 60);
7 | return `${minutes} minute${minutes > 1 ? 's' : ''}${remainingSeconds > 0 ? ` ${remainingSeconds} second${remainingSeconds > 1 ? 's' : ''}` : ''}`;
8 | } else if (seconds < 86400) {
9 | const hours = Math.floor(seconds / 3600);
10 | const minutes = Math.floor((seconds % 3600) / 60);
11 | return `${hours} hour${hours > 1 ? 's' : ''}${minutes > 0 ? ` ${minutes} minute${minutes > 1 ? 's' : ''}` : ''}`;
12 | } else {
13 | const days = Math.floor(seconds / 86400);
14 | const hours = Math.floor((seconds % 86400) / 3600);
15 | return `${days} day${days > 1 ? 's' : ''}${hours > 0 ? ` ${hours} hour${hours > 1 ? 's' : ''}` : ''}`;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/ChatDemo/utils/get-search-url.ts:
--------------------------------------------------------------------------------
1 | export const getSearchUrl = (query: string) => {
2 | const prefix = '';
3 | return `${prefix}?q=${encodeURIComponent(query)}`;
4 | };
5 |
--------------------------------------------------------------------------------
/src/components/Layout/index.tsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import React, { ReactNode } from 'react';
3 |
4 | import { Footer } from '@/components/shared/Footer';
5 | import { Navbar } from '@/components/shared/NavBar';
6 | import { Toaster } from '@/components/ui/toaster';
7 | import { brandingConfig } from '@/config/brandingConfig';
8 |
9 | type Props = {
10 | children: ReactNode;
11 | pageTitle?: string;
12 | includeFooter?: boolean;
13 | };
14 |
15 | const Layout: React.FC = ({
16 | children,
17 | pageTitle,
18 | includeFooter = true,
19 | }) => {
20 | return (
21 |
22 |
23 |
24 | {pageTitle
25 | ? `${pageTitle} | ${brandingConfig.deploymentName}`
26 | : brandingConfig.deploymentName}
27 |
28 |
29 |
30 | {children}
31 |
32 | {includeFooter && }
33 |
34 | );
35 | };
36 |
37 | export default Layout;
38 |
--------------------------------------------------------------------------------
/src/components/OverlayWrapper.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | interface OverlayWrapperProps {
4 | children: React.ReactNode;
5 | message?: string;
6 | }
7 |
8 | const OverlayWrapper: React.FC = ({
9 | children,
10 | message = 'More data needs to be collected before this insight is available 🚀',
11 | }) => {
12 | return (
13 |
14 | {children}
15 |
16 |
17 | {message}
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | export default OverlayWrapper;
25 |
--------------------------------------------------------------------------------
/src/components/Spinner.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { SpinnerProps } from '@/types';
4 |
5 | export const Spinner: React.FC = ({ className }) => {
6 | return (
7 |
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/src/components/ThemeProvider/index.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { ThemeProvider as NextThemesProvider } from 'next-themes';
4 | import { type ThemeProviderProps } from 'next-themes/dist/types';
5 | import * as React from 'react';
6 |
7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
8 | return {children};
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/WorldMapCard.tsx:
--------------------------------------------------------------------------------
1 | import { Mercator, Graticule } from '@visx/geo';
2 | import React, { useEffect, useRef, useState } from 'react';
3 | import * as topojson from 'topojson-client';
4 |
5 | import OverlayWrapper from '@/components/OverlayWrapper';
6 | import {
7 | Card,
8 | CardHeader,
9 | CardTitle,
10 | CardDescription,
11 | CardContent,
12 | } from '@/components/ui/card';
13 | import { brandingConfig } from '@/config/brandingConfig';
14 |
15 | import topology from './world-topo.json';
16 |
17 | export const background = '#1e1e1e';
18 |
19 | interface FeatureShape {
20 | type: 'Feature';
21 | id: string;
22 | geometry: { coordinates: [number, number][][]; type: 'Polygon' };
23 | properties: { name: string };
24 | }
25 |
26 | // @ts-expect-error: TypeScript does not recognize the shape of the topojson feature
27 | const world = topojson.feature(topology, topology.objects.units) as {
28 | type: 'FeatureCollection';
29 | features: FeatureShape[];
30 | };
31 |
32 | export const colors: string[] = [
33 | '#0d47a1',
34 | '#2196f3',
35 | '#1565c0',
36 | '#1976d2',
37 | '#1e88e5',
38 | ]; // Blue color palette
39 |
40 | // Dummy data for number of users per country
41 | const usersPerCountry: Record = {
42 | USA: 1000000,
43 | CHN: 800000,
44 | IND: 750000,
45 | BRA: 600000,
46 | RUS: 500000,
47 | GBR: 400000,
48 | DEU: 300000,
49 | FRA: 200000,
50 | JPN: 100000,
51 | // Add more countries as needed
52 | };
53 |
54 | const useDimensions = () => {
55 | const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
56 | const ref = useRef(null);
57 |
58 | useEffect(() => {
59 | const observeTarget = ref.current;
60 | const resizeObserver = new ResizeObserver((entries) => {
61 | if (entries[0]) {
62 | const { width, height } = entries[0].contentRect;
63 | setDimensions({ width, height });
64 | }
65 | });
66 |
67 | if (observeTarget) {
68 | resizeObserver.observe(observeTarget);
69 | }
70 |
71 | return () => {
72 | if (observeTarget) {
73 | resizeObserver.unobserve(observeTarget);
74 | }
75 | };
76 | }, []);
77 |
78 | return { ref, dimensions };
79 | };
80 |
81 | export default function WorldMapCard() {
82 | const { ref, dimensions } = useDimensions();
83 | const { width, height } = dimensions;
84 |
85 | const centerX = width / 2;
86 | const centerY = height / 2;
87 | const scale = (width / 630) * 100;
88 |
89 | const getColor = (countryId: string) => {
90 | const userCount = usersPerCountry[countryId] || 0;
91 | const index = Math.min(Math.floor(userCount / 200000), colors.length - 1);
92 | return colors[index];
93 | };
94 |
95 | return (
96 |
97 |
98 |
99 | Users by Region: World
100 |
101 |
102 | Requests to your {brandingConfig.deploymentName} server over the past
103 | 24 hours.
104 |
105 |
106 |
107 |
112 | {width > 0 && height > 0 && (
113 |
114 |
139 |
140 | )}
141 |
142 |
143 |
144 | );
145 | }
146 |
--------------------------------------------------------------------------------
/src/components/shared/Footer.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import Link from 'next/link';
4 |
5 | import { brandingConfig } from '@/config/brandingConfig';
6 |
7 | function XIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
8 | return (
9 |
12 | );
13 | }
14 |
15 | function GitHubIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
16 | return (
17 |
24 | );
25 | }
26 |
27 | function DiscordIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
28 | return (
29 |
32 | );
33 | }
34 |
35 | function SocialLink({
36 | href,
37 | icon: Icon,
38 | children,
39 | }: {
40 | href: string;
41 | icon: React.ComponentType<{ className?: string }>;
42 | children: React.ReactNode;
43 | }) {
44 | return (
45 | href && (
46 |
47 | {children}
48 |
49 |
50 | )
51 | );
52 | }
53 |
54 | function SmallPrint() {
55 | return (
56 |
57 |
58 | © Copyright {new Date().getFullYear()}.{' '}
59 | {brandingConfig.companyName} All rights reserved.
60 |
61 |
62 | {brandingConfig.socialLinks.twitter.enabled && (
63 |
67 | Follow us on X
68 |
69 | )}
70 | {brandingConfig.socialLinks.github.enabled && (
71 |
75 | Follow us on GitHub
76 |
77 | )}
78 | {brandingConfig.socialLinks.discord.enabled && (
79 |
83 | Join our Discord server
84 |
85 | )}
86 |
87 |
88 | );
89 | }
90 |
91 | export function Footer() {
92 | return (
93 |
96 | );
97 | }
98 |
--------------------------------------------------------------------------------
/src/components/shared/Logo.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image';
2 | import Link from 'next/link';
3 | import React from 'react';
4 |
5 | import { brandingConfig } from '@/config/brandingConfig';
6 |
7 | interface LogoProps {
8 | width?: number;
9 | height?: number;
10 | className?: string;
11 | onClick?: () => void;
12 | disableLink?: boolean;
13 | }
14 |
15 | export function Logo({
16 | width = 25,
17 | height = 25,
18 | className = '',
19 | onClick,
20 | disableLink = false,
21 | ...rest
22 | }: LogoProps) {
23 | const handleClick = (e: React.MouseEvent) => {
24 | if (onClick) {
25 | e.preventDefault();
26 | onClick();
27 | }
28 | };
29 |
30 | const imageElement = (
31 |
40 | );
41 |
42 | const combinedClassName =
43 | `${className} ${disableLink ? 'cursor-default' : 'cursor-pointer'}`.trim();
44 |
45 | if (disableLink) {
46 | return (
47 |
48 | {imageElement}
49 |
50 | );
51 | }
52 |
53 | return (
54 |
55 | {imageElement}
56 |
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/src/components/shared/ThemeToggle.tsx:
--------------------------------------------------------------------------------
1 | import { useTheme } from 'next-themes';
2 | import { useEffect, useState } from 'react';
3 |
4 | function SunIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
5 | return (
6 |
13 | );
14 | }
15 |
16 | function MoonIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
17 | return (
18 |
21 | );
22 | }
23 |
24 | export function ThemeToggle() {
25 | const { resolvedTheme, setTheme } = useTheme();
26 | const otherTheme = resolvedTheme === 'dark' ? 'light' : 'dark';
27 | const [mounted, setMounted] = useState(false);
28 |
29 | useEffect(() => {
30 | setMounted(true);
31 | }, []);
32 |
33 | return (
34 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/src/components/ui/BarChart.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Chart,
3 | CategoryScale,
4 | LinearScale,
5 | BarElement,
6 | Title,
7 | Tooltip,
8 | Legend,
9 | ChartOptions,
10 | } from 'chart.js';
11 | import React from 'react';
12 | import { Bar } from 'react-chartjs-2';
13 | import resolveConfig from 'tailwindcss/resolveConfig';
14 |
15 | import { BarChartProps } from '@/types';
16 |
17 | import tailwindConfig from '../../../tailwind.config';
18 |
19 | const fullConfig = resolveConfig(tailwindConfig);
20 |
21 | Chart.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
22 |
23 | const textColor = fullConfig.theme.colors.gray[300];
24 |
25 | const defaultColors = [
26 | fullConfig.theme.colors.blue[500],
27 | fullConfig.theme.colors.red[500],
28 | fullConfig.theme.colors.yellow[500],
29 | fullConfig.theme.colors.teal[500],
30 | fullConfig.theme.colors.purple[500],
31 | fullConfig.theme.colors.orange[500],
32 | ];
33 |
34 | type FilterDisplayNames = {
35 | [key: string]: string;
36 | search_latency: string;
37 | search_metrics: string;
38 | rag_generation_latency: string;
39 | error: string;
40 | };
41 |
42 | const filterDisplayNames: FilterDisplayNames = {
43 | search_latency: 'Search Latency',
44 | search_metrics: 'Search Metrics',
45 | rag_generation_latency: 'RAG Latency',
46 | error: 'Errors',
47 | };
48 |
49 | const createHistogramData = (data: number[], label: string) => {
50 | if (!Array.isArray(data)) {
51 | console.error('Data passed to createHistogramData is not an array:', data);
52 | return {
53 | labels: [],
54 | datasets: [],
55 | };
56 | }
57 |
58 | const min = Math.min(...data);
59 | const max = Math.max(...data);
60 | const binCount = 10;
61 | const binSize = (max - min) / binCount;
62 |
63 | const bins = Array.from({ length: binCount }, (_, i) => min + i * binSize);
64 | const histogram = bins.map((bin, index) => {
65 | const nextBin = bins[index + 1] ?? max + binSize;
66 | if (index === bins.length - 1) {
67 | return data.filter((value) => value >= bin && value <= max).length;
68 | }
69 | return data.filter((value) => value >= bin && value < nextBin).length;
70 | });
71 |
72 | return {
73 | labels: bins.map((bin, index) => {
74 | const nextBin = bins[index + 1] ?? max;
75 | return `${bin.toFixed(1)} - ${nextBin.toFixed(1)}`;
76 | }),
77 | datasets: [
78 | {
79 | label,
80 | backgroundColor: defaultColors[0],
81 | borderColor: defaultColors[0],
82 | borderWidth: 1,
83 | data: histogram,
84 | barPercentage: 1,
85 | categoryPercentage: 1,
86 | },
87 | ],
88 | };
89 | };
90 |
91 | const BarChart: React.FC = ({ data, selectedFilter }) => {
92 | const filteredLogs = data.filtered_logs?.[selectedFilter] || [];
93 |
94 | const values = filteredLogs.map((entry) => parseFloat(entry.value));
95 | const chartData = createHistogramData(
96 | values,
97 | filterDisplayNames[selectedFilter] || selectedFilter
98 | );
99 |
100 | const chartOptions: ChartOptions<'bar'> = {
101 | responsive: true,
102 | plugins: {
103 | legend: {
104 | position: 'top',
105 | labels: {
106 | color: textColor,
107 | },
108 | },
109 | title: {
110 | display: true,
111 | text: `${filterDisplayNames[selectedFilter] || selectedFilter} Histogram`,
112 | color: textColor,
113 | },
114 | tooltip: {
115 | callbacks: {
116 | label: (context) => {
117 | const label = context.dataset.label || '';
118 | const value = context.parsed.y;
119 | const range = context.label;
120 | return `${label}: ${value} (Range: ${range})`;
121 | },
122 | },
123 | },
124 | },
125 | scales: {
126 | x: {
127 | title: {
128 | display: true,
129 | text: filterDisplayNames[selectedFilter] || selectedFilter,
130 | color: textColor,
131 | },
132 | ticks: {
133 | color: textColor,
134 | maxRotation: 45,
135 | minRotation: 45,
136 | },
137 | grid: {
138 | offset: true,
139 | },
140 | },
141 | y: {
142 | title: {
143 | display: true,
144 | text: 'Count',
145 | color: textColor,
146 | },
147 | ticks: {
148 | color: textColor,
149 | },
150 | beginAtZero: true,
151 | },
152 | },
153 | };
154 |
155 | return (
156 |
157 |
158 | {filteredLogs.length === 0 && (
159 |
160 | No data available
161 |
162 | )}
163 |
164 | );
165 | };
166 |
167 | export default BarChart;
168 |
--------------------------------------------------------------------------------
/src/components/ui/Button.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 | import Link from 'next/link';
3 | import React from 'react';
4 |
5 | import {
6 | Tooltip,
7 | TooltipTrigger,
8 | TooltipContent,
9 | TooltipProvider,
10 | } from '@/components/ui/tooltip';
11 |
12 | const colorStyles = {
13 | primary:
14 | 'bg-zinc-900 text-white hover:bg-zinc-700 dark:bg-accent-darker dark:text-accent-contrast dark:ring-1 dark:ring-inset dark:ring-accent-dark dark:hover:bg-accent-dark dark:hover:text-accent-contrast dark:hover:ring-accent-base',
15 | secondary:
16 | 'text-zinc-900 hover:bg-zinc-200 dark:bg-zinc-800/40 dark:text-zinc-400 dark:ring-1 dark:ring-inset dark:ring-zinc-800 dark:hover:bg-zinc-800 dark:hover:text-zinc-300',
17 | filled:
18 | 'bg-zinc-900 text-white hover:bg-zinc-700 dark:bg-accent-darker dark:text-white dark:hover:bg-accent-dark',
19 | danger:
20 | 'bg-red-600 text-white hover:bg-red-500 dark:bg-red-600 dark:text-white dark:hover:bg-red-500',
21 | amber:
22 | 'bg-amber-500 text-white hover:bg-amber-600 dark:bg-amber-500 dark:text-white dark:hover:bg-amber-600',
23 | blue: 'bg-zinc-900 text-white hover:bg-zinc-700 dark:bg-blue-600/10 dark:text-blue-600 dark:ring-1 dark:ring-inset dark:ring-blue-600/20 dark:hover:bg-blue-600/10 dark:hover:text-blue-300',
24 | blue_filled:
25 | 'bg-zinc-900 text-white hover:bg-zinc-700 dark:bg-blue-600 dark:text-white dark:hover:bg-opacity-90',
26 | text: 'text-accent-base hover:text-accent-dark dark:text-accent-base dark:hover:text-accent-dark',
27 | text_gray:
28 | 'text-gray-200 hover:text-gray-600 dark:text-gray-200 dark:hover:text-gray-500',
29 | disabled: 'bg-zinc-600 text-white cursor-not-allowed hover:bg-zinc-500',
30 | light: 'bg-zinc-700 text-white hover:bg-zinc-600',
31 | transparent:
32 | 'bg-transparent text-zinc-900 hover:bg-zinc-200 dark:text-zinc-400 dark:hover:bg-zinc-800',
33 | };
34 |
35 | const shapeStyles = {
36 | default: 'rounded-md px-1 py-1',
37 | rounded: 'rounded-full px-1 py-1',
38 | outline:
39 | 'px-1 py-1 ring-1 ring-inset ring-zinc-900/10 dark:ring-white/10 rounded-md',
40 | outline_wide:
41 | 'px-2 py-1 ring-1 ring-inset ring-zinc-900/10 dark:ring-white/10 rounded-md',
42 | slim: 'rounded-md px-0.5 py-0.5 h-9 flex items-center justify-center',
43 | };
44 |
45 | type ButtonBaseProps = {
46 | color?: keyof typeof colorStyles;
47 | shape?: keyof typeof shapeStyles;
48 | className?: string;
49 | disabled?: boolean;
50 | children: React.ReactNode;
51 | as?: 'button' | 'anchor';
52 | };
53 |
54 | export type ButtonAsButton = ButtonBaseProps & {
55 | as?: 'button';
56 | href?: undefined;
57 | } & React.ButtonHTMLAttributes;
58 |
59 | type ButtonAsAnchor = ButtonBaseProps & {
60 | as: 'anchor';
61 | href: string;
62 | } & React.AnchorHTMLAttributes;
63 |
64 | interface ButtonProps extends ButtonBaseProps {
65 | as?: 'button' | 'anchor';
66 | href?: string;
67 | }
68 |
69 | type ButtonAttributes = Omit<
70 | React.ButtonHTMLAttributes,
71 | keyof ButtonProps
72 | >;
73 | type AnchorAttributes = Omit<
74 | React.AnchorHTMLAttributes,
75 | keyof ButtonProps
76 | >;
77 |
78 | interface ButtonWithTooltipProps extends ButtonProps {
79 | tooltip?: React.ReactNode;
80 | }
81 |
82 | const Button = React.forwardRef<
83 | HTMLButtonElement | HTMLAnchorElement,
84 | ButtonWithTooltipProps & (ButtonAttributes | AnchorAttributes)
85 | >((props, ref) => {
86 | const {
87 | as = 'button',
88 | color = 'primary',
89 | shape = 'default',
90 | className,
91 | children,
92 | href,
93 | disabled = false,
94 | tooltip,
95 | ...restProps
96 | } = props;
97 |
98 | const buttonColor = disabled ? 'disabled' : color;
99 |
100 | const buttonClassName = clsx(
101 | 'inline-flex gap-0.5 justify-center overflow-hidden font-medium transition',
102 | colorStyles[buttonColor],
103 | shapeStyles[shape],
104 | className
105 | );
106 |
107 | let ButtonElement: React.ReactElement;
108 |
109 | if (as === 'anchor') {
110 | if (!href) {
111 | throw new Error(
112 | "The 'href' prop is required when using Button as 'anchor'."
113 | );
114 | }
115 |
116 | ButtonElement = (
117 |
118 | }
120 | className={buttonClassName}
121 | {...(restProps as AnchorAttributes)}
122 | >
123 | {children}
124 |
125 |
126 | );
127 | } else {
128 | ButtonElement = (
129 |
137 | );
138 | }
139 |
140 | if (tooltip) {
141 | return (
142 |
143 |
144 | {ButtonElement}
145 | {tooltip}
146 |
147 |
148 | );
149 | }
150 |
151 | return ButtonElement;
152 | });
153 |
154 | Button.displayName = 'Button';
155 |
156 | export { Button };
157 |
--------------------------------------------------------------------------------
/src/components/ui/CopyableContent.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { useToast } from '@/components/ui/use-toast';
4 |
5 | interface CopyableContentProps {
6 | content: string;
7 | truncated: string;
8 | }
9 |
10 | const CopyableContent: React.FC = ({
11 | content,
12 | truncated,
13 | }) => {
14 | const { toast } = useToast();
15 |
16 | const handleCopy = () => {
17 | navigator.clipboard
18 | .writeText(content)
19 | .then(() => toast({ title: 'Copied!' }))
20 | .catch((err) => console.error('Could not copy text: ', err));
21 | };
22 |
23 | return (
24 |
29 | {truncated}
30 |
31 | );
32 | };
33 |
34 | export default CopyableContent;
35 |
--------------------------------------------------------------------------------
/src/components/ui/InfoIcon.tsx:
--------------------------------------------------------------------------------
1 | type InfoIconProps = {
2 | width?: string;
3 | height?: string;
4 | backgroundColor?: string;
5 | textColor?: string;
6 | className?: string;
7 | };
8 |
9 | export const InfoIcon: React.FC = ({
10 | width = 'w-6',
11 | height = 'h-6',
12 | backgroundColor = 'bg-accent-dark',
13 | textColor = 'text-white',
14 | className = '',
15 | }) => (
16 |
19 | i
20 |
21 | );
22 |
--------------------------------------------------------------------------------
/src/components/ui/ModelSelector.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | import {
4 | Dialog,
5 | DialogContent,
6 | DialogHeader,
7 | DialogTitle,
8 | DialogDescription,
9 | } from '@/components/ui/dialog';
10 | import {
11 | Select,
12 | SelectContent,
13 | SelectItem,
14 | SelectTrigger,
15 | SelectValue,
16 | } from '@/components/ui/select';
17 | import { useUserContext } from '@/context/UserContext';
18 | import { ModelSelectorProps } from '@/types';
19 |
20 | import { Button } from './Button';
21 |
22 | const predefinedModels = [
23 | { value: 'gpt-4o-mini', label: 'gpt-4o-mini' },
24 | { value: 'gpt-4o', label: 'gpt-4o' },
25 | { value: 'ollama/llama3.1', label: 'ollama/llama3.1' },
26 | ];
27 |
28 | const ModelSelector: React.FC = ({ id }) => {
29 | const { selectedModel, setSelectedModel } = useUserContext();
30 | const [isDialogOpen, setIsDialogOpen] = useState(false);
31 | const [customModelValue, setCustomModelValue] = useState('');
32 | const [allModels, setAllModels] = useState(predefinedModels);
33 |
34 | useEffect(() => {}, [selectedModel, allModels]);
35 |
36 | const handleSelectChange = (value: string) => {
37 | if (value === 'add_custom') {
38 | setIsDialogOpen(true);
39 | } else {
40 | setSelectedModel(value);
41 | }
42 | };
43 |
44 | const handleCustomModelChange = (e: React.ChangeEvent) => {
45 | setCustomModelValue(e.target.value || null);
46 | };
47 |
48 | const handleCustomModelSubmit = () => {
49 | if (customModelValue !== null && customModelValue.trim() !== '') {
50 | const trimmedValue = customModelValue.trim();
51 | const newModel = { value: trimmedValue, label: trimmedValue };
52 | setAllModels((prevModels) => [...prevModels, newModel]);
53 | setSelectedModel(trimmedValue);
54 | setCustomModelValue(null);
55 | setIsDialogOpen(false);
56 | } else {
57 | console.warn('Attempted to submit empty custom model name');
58 | }
59 | };
60 |
61 | return (
62 |
63 |
76 |
77 |
103 |
104 | );
105 | };
106 |
107 | export default ModelSelector;
108 |
--------------------------------------------------------------------------------
/src/components/ui/ShadcnButton.tsx:
--------------------------------------------------------------------------------
1 | import { Slot } from '@radix-ui/react-slot';
2 | import { cva, type VariantProps } from 'class-variance-authority';
3 | import * as React from 'react';
4 |
5 | import { cn } from '@/lib/utils';
6 |
7 | const buttonVariants = cva(
8 | 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
9 | {
10 | variants: {
11 | variant: {
12 | default: 'bg-primary text-primary-foreground hover:bg-primary/90',
13 | destructive:
14 | 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
15 | outline:
16 | 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
17 | secondary:
18 | 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
19 | ghost: 'hover:bg-accent hover:text-accent-foreground',
20 | link: 'text-primary underline-offset-4 hover:underline',
21 | },
22 | size: {
23 | default: 'h-10 px-4 py-2',
24 | sm: 'h-9 rounded-md px-3',
25 | lg: 'h-11 rounded-md px-8',
26 | icon: 'h-10 w-10',
27 | },
28 | },
29 | defaultVariants: {
30 | variant: 'default',
31 | size: 'default',
32 | },
33 | }
34 | );
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean;
40 | }
41 |
42 | const ShadcnButton = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : 'button';
45 | return (
46 |
51 | );
52 | }
53 | );
54 | ShadcnButton.displayName = 'Button';
55 |
56 | export { ShadcnButton, buttonVariants };
57 |
--------------------------------------------------------------------------------
/src/components/ui/UserSelector.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | import {
4 | Dialog,
5 | DialogContent,
6 | DialogHeader,
7 | DialogTitle,
8 | DialogDescription,
9 | } from '@/components/ui/dialog';
10 | import {
11 | Select,
12 | SelectContent,
13 | SelectItem,
14 | SelectTrigger,
15 | SelectValue,
16 | } from '@/components/ui/select';
17 |
18 | import { Button } from './Button';
19 |
20 | interface UserSelectorProps {
21 | id?: string;
22 | selectedUserId: string;
23 | setSelectedUserId: (userId: string) => void;
24 | }
25 |
26 | const UserSelector: React.FC = ({
27 | id,
28 | selectedUserId,
29 | setSelectedUserId,
30 | }) => {
31 | const [isDialogOpen, setIsDialogOpen] = useState(false);
32 |
33 | const handleValueChange = (value: string) => {
34 | if (value === 'addUser') {
35 | setIsDialogOpen(true);
36 | } else {
37 | setSelectedUserId(value);
38 | }
39 | };
40 |
41 | const handleAddUser = () => {
42 | const newUserId = generatePlaceholderUserId();
43 | setSelectedUserId(newUserId);
44 | setIsDialogOpen(false);
45 | };
46 |
47 | const generatePlaceholderUserId = () => {
48 | return 'user-' + Math.random().toString(36).substr(2, 9);
49 | };
50 |
51 | return (
52 | <>
53 |
54 |
70 |
71 |
89 |
90 | >
91 | );
92 | };
93 |
94 | export default UserSelector;
95 |
--------------------------------------------------------------------------------
/src/components/ui/WatchButton.tsx:
--------------------------------------------------------------------------------
1 | import { Button } from '@/components/ui/Button';
2 |
3 | type WatchButtonProps = {
4 | isLoading: boolean;
5 | isPipelineNameValid: boolean;
6 | isDeploymentUrlValid: boolean;
7 | onClick: () => void;
8 | };
9 |
10 | function WatchButton({
11 | isLoading,
12 | isPipelineNameValid,
13 | isDeploymentUrlValid,
14 | onClick,
15 | }: WatchButtonProps) {
16 | const isDisabled = !isPipelineNameValid || !isDeploymentUrlValid || isLoading;
17 |
18 | return (
19 |
27 | );
28 | }
29 |
30 | export { WatchButton };
31 |
--------------------------------------------------------------------------------
/src/components/ui/accordion.tsx:
--------------------------------------------------------------------------------
1 | import * as AccordionPrimitive from '@radix-ui/react-accordion';
2 | import { ChevronDown } from 'lucide-react';
3 | import * as React from 'react';
4 |
5 | import { cn } from '@/lib/utils';
6 |
7 | const Accordion = AccordionPrimitive.Root;
8 |
9 | const AccordionItem = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
18 | ));
19 | AccordionItem.displayName = 'AccordionItem';
20 |
21 | const AccordionTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, children, ...props }, ref) => (
25 |
26 | svg]:rotate-180',
30 | className
31 | )}
32 | {...props}
33 | >
34 | {children}
35 |
36 |
37 |
38 | ));
39 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
40 |
41 | const AccordionContent = React.forwardRef<
42 | React.ElementRef,
43 | React.ComponentPropsWithoutRef
44 | >(({ className, children, ...props }, ref) => (
45 |
50 | {children}
51 |
52 | ));
53 |
54 | AccordionContent.displayName = AccordionPrimitive.Content.displayName;
55 |
56 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
57 |
--------------------------------------------------------------------------------
/src/components/ui/alert-dialog.tsx:
--------------------------------------------------------------------------------
1 | import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
2 | import * as React from 'react';
3 |
4 | import { buttonVariants } from '@/components/ui/ShadcnButton';
5 | import { cn } from '@/lib/utils';
6 |
7 | const AlertDialog = AlertDialogPrimitive.Root;
8 |
9 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
10 |
11 | const AlertDialogPortal = AlertDialogPrimitive.Portal;
12 |
13 | const AlertDialogOverlay = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef
16 | >(({ className, ...props }, ref) => (
17 |
25 | ));
26 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
27 |
28 | const AlertDialogContent = React.forwardRef<
29 | React.ElementRef,
30 | React.ComponentPropsWithoutRef
31 | >(({ className, ...props }, ref) => (
32 |
33 |
34 |
42 |
43 | ));
44 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
45 |
46 | const AlertDialogHeader = ({
47 | className,
48 | ...props
49 | }: React.HTMLAttributes) => (
50 |
57 | );
58 | AlertDialogHeader.displayName = 'AlertDialogHeader';
59 |
60 | const AlertDialogFooter = ({
61 | className,
62 | ...props
63 | }: React.HTMLAttributes) => (
64 |
71 | );
72 | AlertDialogFooter.displayName = 'AlertDialogFooter';
73 |
74 | const AlertDialogTitle = React.forwardRef<
75 | React.ElementRef,
76 | React.ComponentPropsWithoutRef
77 | >(({ className, ...props }, ref) => (
78 |
83 | ));
84 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
85 |
86 | const AlertDialogDescription = React.forwardRef<
87 | React.ElementRef,
88 | React.ComponentPropsWithoutRef
89 | >(({ className, ...props }, ref) => (
90 |
95 | ));
96 | AlertDialogDescription.displayName =
97 | AlertDialogPrimitive.Description.displayName;
98 |
99 | const AlertDialogAction = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ));
109 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
110 |
111 | const AlertDialogCancel = React.forwardRef<
112 | React.ElementRef,
113 | React.ComponentPropsWithoutRef
114 | >(({ className, ...props }, ref) => (
115 |
124 | ));
125 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
126 |
127 | export {
128 | AlertDialog,
129 | AlertDialogPortal,
130 | AlertDialogOverlay,
131 | AlertDialogTrigger,
132 | AlertDialogContent,
133 | AlertDialogHeader,
134 | AlertDialogFooter,
135 | AlertDialogTitle,
136 | AlertDialogDescription,
137 | AlertDialogAction,
138 | AlertDialogCancel,
139 | };
140 |
--------------------------------------------------------------------------------
/src/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import { cva, type VariantProps } from 'class-variance-authority';
2 | import { X } from 'lucide-react';
3 | import * as React from 'react';
4 |
5 | import { cn } from '@/lib/utils';
6 |
7 | const alertVariants = cva(
8 | 'relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
9 | {
10 | variants: {
11 | variant: {
12 | default: 'bg-background text-foreground',
13 | destructive:
14 | 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
15 | },
16 | },
17 | defaultVariants: {
18 | variant: 'default',
19 | },
20 | }
21 | );
22 |
23 | const Alert = React.forwardRef<
24 | HTMLDivElement,
25 | React.HTMLAttributes &
26 | VariantProps & { onClose?: () => void }
27 | >((({ className, variant, onClose, ...props }, ref) => (
28 |
34 | {onClose && (
35 |
42 | )}
43 | {props.children}
44 |
45 | )) as React.ForwardRefRenderFunction<
46 | HTMLDivElement,
47 | React.HTMLAttributes &
48 | VariantProps & { onClose?: () => void }
49 | >);
50 | Alert.displayName = 'Alert';
51 |
52 | const AlertTitle = React.forwardRef<
53 | HTMLParagraphElement,
54 | React.HTMLAttributes
55 | >(({ className, ...props }, ref) => (
56 |
61 | ));
62 | AlertTitle.displayName = 'AlertTitle';
63 |
64 | const AlertDescription = React.forwardRef<
65 | HTMLParagraphElement,
66 | React.HTMLAttributes
67 | >(({ className, ...props }, ref) => (
68 |
73 | ));
74 | AlertDescription.displayName = 'AlertDescription';
75 |
76 | export { Alert, AlertTitle, AlertDescription };
77 |
--------------------------------------------------------------------------------
/src/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | import * as AvatarPrimitive from '@radix-ui/react-avatar';
2 | import * as React from 'react';
3 |
4 | import { cn } from '@/lib/utils';
5 |
6 | const Avatar = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
18 | ));
19 | Avatar.displayName = AvatarPrimitive.Root.displayName;
20 |
21 | const AvatarImage = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, ...props }, ref) => (
25 |
30 | ));
31 | AvatarImage.displayName = AvatarPrimitive.Image.displayName;
32 |
33 | const AvatarFallback = React.forwardRef<
34 | React.ElementRef,
35 | React.ComponentPropsWithoutRef
36 | >(({ className, ...props }, ref) => (
37 |
45 | ));
46 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
47 |
48 | export { Avatar, AvatarImage, AvatarFallback };
49 |
--------------------------------------------------------------------------------
/src/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import { cva, type VariantProps } from 'class-variance-authority';
2 | import * as React from 'react';
3 |
4 | import { cn } from '@/lib/utils';
5 |
6 | const badgeVariants = cva(
7 | 'inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | 'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
13 | secondary:
14 | 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
15 | destructive:
16 | 'border-red-600 bg-red-600 text-destructive-foreground shadow hover:bg-destructive/80',
17 | outline: 'text-foreground',
18 | success: 'border-accent-darker bg-accent-darker text-white',
19 | pending: 'border-yellow-500 bg-yellow-500 text-white',
20 | },
21 | },
22 | defaultVariants: {
23 | variant: 'default',
24 | },
25 | }
26 | );
27 |
28 | export interface BadgeProps
29 | extends React.HTMLAttributes,
30 | VariantProps {}
31 |
32 | function Badge({ className, variant, ...props }: BadgeProps) {
33 | return (
34 |
35 | );
36 | }
37 |
38 | export { Badge, badgeVariants };
39 |
--------------------------------------------------------------------------------
/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { cn } from '@/lib/utils';
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
18 | ));
19 | Card.displayName = 'Card';
20 |
21 | const CardHeader = React.forwardRef<
22 | HTMLDivElement,
23 | React.HTMLAttributes
24 | >(({ className, ...props }, ref) => (
25 |
30 | ));
31 | CardHeader.displayName = 'CardHeader';
32 |
33 | const CardTitle = React.forwardRef<
34 | HTMLParagraphElement,
35 | React.HTMLAttributes
36 | >(({ className, ...props }, ref) => (
37 |
45 | ));
46 | CardTitle.displayName = 'CardTitle';
47 |
48 | const CardDescription = React.forwardRef<
49 | HTMLParagraphElement,
50 | React.HTMLAttributes
51 | >(({ className, ...props }, ref) => (
52 |
57 | ));
58 | CardDescription.displayName = 'CardDescription';
59 |
60 | const CardContent = React.forwardRef<
61 | HTMLDivElement,
62 | React.HTMLAttributes
63 | >(({ className, ...props }, ref) => (
64 |
65 | ));
66 | CardContent.displayName = 'CardContent';
67 |
68 | const CardFooter = React.forwardRef<
69 | HTMLDivElement,
70 | React.HTMLAttributes
71 | >(({ className, ...props }, ref) => (
72 |
77 | ));
78 | CardFooter.displayName = 'CardFooter';
79 |
80 | export {
81 | Card,
82 | CardHeader,
83 | CardFooter,
84 | CardTitle,
85 | CardDescription,
86 | CardContent,
87 | };
88 |
--------------------------------------------------------------------------------
/src/components/ui/checkbox.tsx:
--------------------------------------------------------------------------------
1 | import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
2 | import { Check } from 'lucide-react';
3 | import * as React from 'react';
4 |
5 | import { cn } from '@/lib/utils';
6 |
7 | const Checkbox = React.forwardRef<
8 | React.ElementRef,
9 | React.ComponentPropsWithoutRef
10 | >(({ className, ...props }, ref) => (
11 |
19 |
22 |
23 |
24 |
25 | ));
26 | Checkbox.displayName = CheckboxPrimitive.Root.displayName;
27 |
28 | export { Checkbox };
29 |
--------------------------------------------------------------------------------
/src/components/ui/collapsible.tsx:
--------------------------------------------------------------------------------
1 | import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
2 |
3 | const Collapsible = CollapsiblePrimitive.Root;
4 |
5 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
6 |
7 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
8 |
9 | export { Collapsible, CollapsibleTrigger, CollapsibleContent };
10 |
--------------------------------------------------------------------------------
/src/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | import * as DialogPrimitive from '@radix-ui/react-dialog';
2 | import { X } from 'lucide-react';
3 | import * as React from 'react';
4 |
5 | import { cn } from '@/lib/utils';
6 |
7 | const Dialog = DialogPrimitive.Root;
8 |
9 | const DialogTrigger = DialogPrimitive.Trigger;
10 |
11 | const DialogPortal = DialogPrimitive.Portal;
12 |
13 | const DialogClose = DialogPrimitive.Close;
14 |
15 | const DialogOverlay = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, ...props }, ref) => (
19 |
27 | ));
28 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
29 |
30 | const DialogContent = React.forwardRef<
31 | React.ElementRef,
32 | React.ComponentPropsWithoutRef
33 | >(({ className, children, ...props }, ref) => (
34 |
35 |
36 |
44 | {children}
45 |
46 |
47 | Close
48 |
49 |
50 |
51 | ));
52 | DialogContent.displayName = DialogPrimitive.Content.displayName;
53 |
54 | const DialogHeader = ({
55 | className,
56 | ...props
57 | }: React.HTMLAttributes) => (
58 |
65 | );
66 | DialogHeader.displayName = 'DialogHeader';
67 |
68 | const DialogFooter = ({
69 | className,
70 | ...props
71 | }: React.HTMLAttributes) => (
72 |
79 | );
80 | DialogFooter.displayName = 'DialogFooter';
81 |
82 | const DialogTitle = React.forwardRef<
83 | React.ElementRef,
84 | React.ComponentPropsWithoutRef
85 | >(({ className, ...props }, ref) => (
86 |
94 | ));
95 | DialogTitle.displayName = DialogPrimitive.Title.displayName;
96 |
97 | const DialogDescription = React.forwardRef<
98 | React.ElementRef,
99 | React.ComponentPropsWithoutRef
100 | >(({ className, ...props }, ref) => (
101 |
106 | ));
107 | DialogDescription.displayName = DialogPrimitive.Description.displayName;
108 |
109 | export {
110 | Dialog,
111 | DialogPortal,
112 | DialogOverlay,
113 | DialogClose,
114 | DialogTrigger,
115 | DialogContent,
116 | DialogHeader,
117 | DialogFooter,
118 | DialogTitle,
119 | DialogDescription,
120 | };
121 |
--------------------------------------------------------------------------------
/src/components/ui/highlight.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { HighlightProps } from '@/types';
4 |
5 | const Highlight: React.FC = ({
6 | color,
7 | textColor,
8 | children,
9 | }) => {
10 | return (
11 |
12 | {children}
13 |
14 | );
15 | };
16 |
17 | export default Highlight;
18 |
--------------------------------------------------------------------------------
/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { cn } from '@/lib/utils';
4 | import { InputProps } from '@/types';
5 |
6 | const Input = React.forwardRef(
7 | ({ className, type, ...props }, ref) => {
8 | return (
9 |
18 | );
19 | }
20 | );
21 | Input.displayName = 'Input';
22 |
23 | export { Input };
24 |
--------------------------------------------------------------------------------
/src/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | import * as LabelPrimitive from '@radix-ui/react-label';
2 | import { cva, type VariantProps } from 'class-variance-authority';
3 | import * as React from 'react';
4 |
5 | import { cn } from '@/lib/utils';
6 |
7 | const labelVariants = cva(
8 | 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
9 | );
10 |
11 | interface LabelProps
12 | extends React.ComponentPropsWithoutRef,
13 | VariantProps {
14 | children?: React.ReactNode;
15 | htmlFor?: string;
16 | }
17 |
18 | const Label = React.forwardRef<
19 | React.ElementRef,
20 | LabelProps
21 | >(({ className, children, htmlFor, ...props }, ref) => (
22 |
28 | {children}
29 |
30 | ));
31 | Label.displayName = LabelPrimitive.Root.displayName;
32 |
33 | export { Label };
34 |
--------------------------------------------------------------------------------
/src/components/ui/logtable.tsx:
--------------------------------------------------------------------------------
1 | import { ChevronDown, ChevronRight } from 'lucide-react';
2 | import React, { useState } from 'react';
3 |
4 | import {
5 | Collapsible,
6 | CollapsibleContent,
7 | CollapsibleTrigger,
8 | } from '@/components/ui/collapsible';
9 | import Pagination from '@/components/ui/pagination';
10 | import {
11 | Table,
12 | TableBody,
13 | TableCell,
14 | TableHead,
15 | TableHeader,
16 | TableRow,
17 | } from '@/components/ui/table';
18 |
19 | const LOGS_PER_PAGE = 10;
20 | const COLLAPSIBLE_THRESHOLD = 100;
21 |
22 | interface Log {
23 | id: string;
24 | run_id: string;
25 | run_type: string;
26 | timestamp: string;
27 | userId: string;
28 | entries: any[];
29 | value?: any;
30 | }
31 |
32 | interface LogTableProps {
33 | logs: Log[];
34 | }
35 |
36 | const LogTable: React.FC = ({ logs }) => {
37 | const [currentPage, setCurrentPage] = useState(1);
38 | const [expandedCells, setExpandedCells] = useState>(
39 | {}
40 | );
41 |
42 | const totalPages = Math.ceil(logs.length / LOGS_PER_PAGE);
43 |
44 | const paginatedLogs = logs.slice(
45 | (currentPage - 1) * LOGS_PER_PAGE,
46 | currentPage * LOGS_PER_PAGE
47 | );
48 |
49 | const handlePageChange = (pageNumber: number) => {
50 | setCurrentPage(pageNumber);
51 | };
52 |
53 | const toggleCell = (logId: string) => {
54 | setExpandedCells((prev) => ({ ...prev, [logId]: !prev[logId] }));
55 | };
56 |
57 | const prettifyJSON = (value: any): string => {
58 | if (typeof value !== 'object') {
59 | return String(value);
60 | }
61 | return JSON.stringify(value, null, 2);
62 | };
63 |
64 | const renderValue = (log: Log) => {
65 | const isEmptyArray = Array.isArray(log.value) && log.value.length === 0;
66 | const isCollapsible =
67 | !isEmptyArray &&
68 | ((typeof log.value === 'string' &&
69 | log.value.length > COLLAPSIBLE_THRESHOLD) ||
70 | (typeof log.value === 'object' && log.value !== null));
71 | const prettyValue = prettifyJSON(log.value);
72 |
73 | if (isEmptyArray) {
74 | return null;
75 | }
76 |
77 | if (isCollapsible) {
78 | return (
79 |
80 | toggleCell(log.id)}
82 | className="flex items-center w-full text-left"
83 | >
84 | {expandedCells[log.id] ? (
85 |
86 | ) : (
87 |
88 | )}
89 |
90 |
91 |
92 | {prettyValue}
93 |
94 |
95 |
96 | );
97 | } else {
98 | return (
99 |
100 | {prettyValue}
101 |
102 | );
103 | }
104 | };
105 |
106 | return (
107 |
108 |
109 |
110 |
111 | Run ID
112 | Run Type
113 | Timestamp
114 | User ID
115 | Entries
116 |
117 |
118 |
119 | {paginatedLogs.map((log) => (
120 |
121 |
122 | {log.run_id
123 | ? `${log.run_id.substring(0, 8)}...${log.run_id.slice(-8)}`
124 | : 'N/A'}
125 |
126 | {log.run_type}
127 | {log.timestamp}
128 |
129 | {log.userId
130 | ? `${log.userId.substring(0, 8)}...${log.userId.slice(-8)}`
131 | : 'N/A'}
132 |
133 | {log.entries && log.entries.length > 0 && (
134 |
135 | {renderValue({ ...log, id: log.run_id, value: log.entries })}
136 |
137 | )}
138 |
139 | ))}
140 |
141 |
142 |
147 |
148 | );
149 | };
150 |
151 | export default LogTable;
152 |
--------------------------------------------------------------------------------
/src/components/ui/multi-select.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Button } from '@/components/ui/Button';
4 | import { Checkbox } from '@/components/ui/checkbox';
5 | import { Label } from '@/components/ui/label';
6 | import {
7 | Popover,
8 | PopoverContent,
9 | PopoverTrigger,
10 | } from '@/components/ui/popover';
11 |
12 | interface Option {
13 | value: string;
14 | label: string;
15 | }
16 |
17 | interface MultiSelectProps {
18 | options: Option[];
19 | value: string[];
20 | onChange: (value: string[]) => void;
21 | id: string;
22 | }
23 |
24 | export const MultiSelect: React.FC = ({
25 | options,
26 | value,
27 | onChange,
28 | id,
29 | }) => {
30 | const handleValueChange = (clickedValue: string) => {
31 | const newValue = value.includes(clickedValue)
32 | ? value.filter((v) => v !== clickedValue)
33 | : [...value, clickedValue];
34 | onChange(newValue);
35 | };
36 |
37 | return (
38 |
39 |
40 |
45 |
46 |
47 |
48 | {options.map((option) => (
49 |
53 | handleValueChange(option.value)}
57 | />
58 |
64 |
65 | ))}
66 |
67 |
68 |
69 | );
70 | };
71 |
--------------------------------------------------------------------------------
/src/components/ui/pagination.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useCallback } from 'react';
2 |
3 | import { Button } from '@/components/ui/Button';
4 | import { Input } from '@/components/ui/input';
5 | import debounce from '@/lib/debounce';
6 |
7 | interface PaginationProps {
8 | currentPage: number;
9 | totalPages: number;
10 | onPageChange: (pageNumber: number) => void;
11 | isLoading?: boolean;
12 | }
13 |
14 | const Pagination: React.FC = ({
15 | currentPage,
16 | totalPages,
17 | onPageChange,
18 | isLoading = false,
19 | }) => {
20 | const [inputPage, setInputPage] = useState(currentPage.toString());
21 |
22 | useEffect(() => {
23 | setInputPage(currentPage.toString());
24 | }, [currentPage]);
25 |
26 | const debouncedPageChange = useCallback(
27 | debounce((page: number) => {
28 | if (page >= 1 && page <= totalPages && !isLoading) {
29 | onPageChange(page);
30 | }
31 | }, 300),
32 | [onPageChange, totalPages, isLoading]
33 | );
34 |
35 | const handlePageChange = (newPage: number) => {
36 | if (newPage >= 1 && newPage <= totalPages && !isLoading) {
37 | onPageChange(newPage);
38 | }
39 | };
40 |
41 | const handleInputChange = (e: React.ChangeEvent) => {
42 | setInputPage(e.target.value);
43 | };
44 |
45 | const handleInputBlur = () => {
46 | const parsedPage = parseInt(inputPage, 10);
47 | if (!isNaN(parsedPage) && parsedPage >= 1 && parsedPage <= totalPages) {
48 | handlePageChange(parsedPage);
49 | } else {
50 | setInputPage(currentPage.toString());
51 | }
52 | };
53 |
54 | const handleInputKeyPress = (e: React.KeyboardEvent) => {
55 | if (e.key === 'Enter') {
56 | handleInputBlur();
57 | }
58 | };
59 |
60 | return (
61 |
92 | );
93 | };
94 |
95 | export default Pagination;
96 |
--------------------------------------------------------------------------------
/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | import * as PopoverPrimitive from '@radix-ui/react-popover';
2 | import * as React from 'react';
3 |
4 | import { cn } from '@/lib/utils';
5 |
6 | const Popover = PopoverPrimitive.Root;
7 |
8 | const PopoverTrigger = PopoverPrimitive.Trigger;
9 |
10 | const PopoverContent = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
14 |
15 |
25 |
26 | ));
27 | PopoverContent.displayName = PopoverPrimitive.Content.displayName;
28 |
29 | export { Popover, PopoverTrigger, PopoverContent };
30 |
--------------------------------------------------------------------------------
/src/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | import * as ProgressPrimitive from '@radix-ui/react-progress';
2 | import * as React from 'react';
3 |
4 | import { cn } from '@/lib/utils';
5 |
6 | const Progress = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, value, ...props }, ref) => (
10 |
18 |
22 |
23 | ));
24 | Progress.displayName = ProgressPrimitive.Root.displayName;
25 |
26 | export { Progress };
27 |
--------------------------------------------------------------------------------
/src/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | interface SeparatorProps {
4 | className?: string;
5 | orientation?: 'horizontal' | 'vertical';
6 | decorative?: boolean;
7 | }
8 |
9 | const Separator = React.forwardRef(
10 | (
11 | { className, orientation = 'horizontal', decorative = false, ...props },
12 | ref
13 | ) => (
14 |
21 | )
22 | );
23 |
24 | Separator.displayName = 'Separator';
25 |
26 | export { Separator };
27 |
--------------------------------------------------------------------------------
/src/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | import * as SheetPrimitive from '@radix-ui/react-dialog';
2 | import { cva, type VariantProps } from 'class-variance-authority';
3 | import { X } from 'lucide-react';
4 | import * as React from 'react';
5 |
6 | import { cn } from '@/lib/utils';
7 |
8 | const Sheet = SheetPrimitive.Root;
9 |
10 | const SheetTrigger = SheetPrimitive.Trigger;
11 |
12 | const SheetClose = SheetPrimitive.Close;
13 |
14 | const SheetPortal = SheetPrimitive.Portal;
15 |
16 | const SheetOverlay = React.forwardRef<
17 | React.ElementRef,
18 | React.ComponentPropsWithoutRef
19 | >(({ className, ...props }, ref) => (
20 |
28 | ));
29 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
30 |
31 | const sheetVariants = cva(
32 | 'fixed z-1000 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
33 | {
34 | variants: {
35 | side: {
36 | top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
37 | bottom:
38 | 'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
39 | left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
40 | right:
41 | 'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
42 | },
43 | },
44 | defaultVariants: {
45 | side: 'right',
46 | },
47 | }
48 | );
49 |
50 | interface SheetContentProps
51 | extends React.ComponentPropsWithoutRef,
52 | VariantProps {}
53 |
54 | const SheetContent = React.forwardRef<
55 | React.ElementRef,
56 | SheetContentProps
57 | >(({ side = 'right', className, children, ...props }, ref) => (
58 |
59 |
60 |
65 | {children}
66 |
67 |
68 | Close
69 |
70 |
71 |
72 | ));
73 | SheetContent.displayName = SheetPrimitive.Content.displayName;
74 |
75 | const SheetHeader = ({
76 | className,
77 | ...props
78 | }: React.HTMLAttributes) => (
79 |
86 | );
87 | SheetHeader.displayName = 'SheetHeader';
88 |
89 | const SheetFooter = ({
90 | className,
91 | ...props
92 | }: React.HTMLAttributes) => (
93 |
100 | );
101 | SheetFooter.displayName = 'SheetFooter';
102 |
103 | const SheetTitle = React.forwardRef<
104 | React.ElementRef,
105 | React.ComponentPropsWithoutRef
106 | >(({ className, ...props }, ref) => (
107 |
112 | ));
113 | SheetTitle.displayName = SheetPrimitive.Title.displayName;
114 |
115 | const SheetDescription = React.forwardRef<
116 | React.ElementRef,
117 | React.ComponentPropsWithoutRef
118 | >(({ className, ...props }, ref) => (
119 |
124 | ));
125 | SheetDescription.displayName = SheetPrimitive.Description.displayName;
126 |
127 | export {
128 | Sheet,
129 | SheetPortal,
130 | SheetOverlay,
131 | SheetTrigger,
132 | SheetClose,
133 | SheetContent,
134 | SheetHeader,
135 | SheetFooter,
136 | SheetTitle,
137 | SheetDescription,
138 | };
139 |
--------------------------------------------------------------------------------
/src/components/ui/slider.tsx:
--------------------------------------------------------------------------------
1 | import * as SliderPrimitive from '@radix-ui/react-slider';
2 | import * as React from 'react';
3 |
4 | import { cn } from '@/lib/utils';
5 |
6 | const Slider = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
18 |
19 |
20 |
21 |
22 |
23 | ));
24 | Slider.displayName = SliderPrimitive.Root.displayName;
25 |
26 | export { Slider };
27 |
--------------------------------------------------------------------------------
/src/components/ui/switch.tsx:
--------------------------------------------------------------------------------
1 | import * as SwitchPrimitives from '@radix-ui/react-switch';
2 | import * as React from 'react';
3 |
4 | import { cn } from '@/lib/utils';
5 |
6 | const Switch = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
18 |
23 |
24 | ));
25 | Switch.displayName = SwitchPrimitives.Root.displayName;
26 |
27 | export { Switch };
28 |
--------------------------------------------------------------------------------
/src/components/ui/table.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { cn } from '@/lib/utils';
4 |
5 | const Table = React.forwardRef<
6 | HTMLTableElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
16 | ));
17 | Table.displayName = 'Table';
18 |
19 | const TableHeader = React.forwardRef<
20 | HTMLTableSectionElement,
21 | React.HTMLAttributes
22 | >(({ className, ...props }, ref) => (
23 |
24 | ));
25 | TableHeader.displayName = 'TableHeader';
26 |
27 | const TableBody = React.forwardRef<
28 | HTMLTableSectionElement,
29 | React.HTMLAttributes
30 | >(({ className, ...props }, ref) => (
31 |
36 | ));
37 | TableBody.displayName = 'TableBody';
38 |
39 | const TableFooter = React.forwardRef<
40 | HTMLTableSectionElement,
41 | React.HTMLAttributes
42 | >(({ className, ...props }, ref) => (
43 | tr]:last:border-b-0',
47 | className
48 | )}
49 | {...props}
50 | />
51 | ));
52 | TableFooter.displayName = 'TableFooter';
53 |
54 | const TableRow = React.forwardRef<
55 | HTMLTableRowElement,
56 | React.HTMLAttributes
57 | >(({ className, ...props }, ref) => (
58 |
66 | ));
67 | TableRow.displayName = 'TableRow';
68 |
69 | const TableHead = React.forwardRef<
70 | HTMLTableCellElement,
71 | React.ThHTMLAttributes
72 | >(({ className, ...props }, ref) => (
73 | |
81 | ));
82 | TableHead.displayName = 'TableHead';
83 |
84 | const TableCell = React.forwardRef<
85 | HTMLTableCellElement,
86 | React.ThHTMLAttributes
87 | >(({ className, ...props }, ref) => {
88 | const classes = cn(
89 | 'p-4 align-middle [&:has([role=checkbox])]:pr-0',
90 | className
91 | );
92 |
93 | return | ;
94 | });
95 |
96 | TableCell.displayName = 'TableCell';
97 |
98 | const TableCaption = React.forwardRef<
99 | HTMLTableCaptionElement,
100 | React.HTMLAttributes
101 | >(({ className, ...props }, ref) => (
102 |
107 | ));
108 | TableCaption.displayName = 'TableCaption';
109 |
110 | export {
111 | Table,
112 | TableHeader,
113 | TableBody,
114 | TableFooter,
115 | TableHead,
116 | TableRow,
117 | TableCell,
118 | TableCaption,
119 | };
120 |
--------------------------------------------------------------------------------
/src/components/ui/tabs.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import * as TabsPrimitive from '@radix-ui/react-tabs';
4 | import * as React from 'react';
5 |
6 | import { cn } from '@/lib/utils';
7 |
8 | const Tabs = TabsPrimitive.Root;
9 |
10 | const TabsList = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ));
23 | TabsList.displayName = TabsPrimitive.List.displayName;
24 |
25 | const TabsTrigger = React.forwardRef<
26 | React.ElementRef,
27 | React.ComponentPropsWithoutRef
28 | >(({ className, ...props }, ref) => (
29 |
37 | ));
38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
39 |
40 | const TabsContent = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
52 | ));
53 | TabsContent.displayName = TabsPrimitive.Content.displayName;
54 |
55 | export { Tabs, TabsList, TabsTrigger, TabsContent };
56 |
--------------------------------------------------------------------------------
/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import { cn } from '@/lib/utils';
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | );
20 | }
21 | );
22 | Textarea.displayName = 'Textarea';
23 |
24 | export { Textarea };
25 |
--------------------------------------------------------------------------------
/src/components/ui/toast.tsx:
--------------------------------------------------------------------------------
1 | import * as ToastPrimitives from '@radix-ui/react-toast';
2 | import { cva, type VariantProps } from 'class-variance-authority';
3 | import { X } from 'lucide-react';
4 | import * as React from 'react';
5 |
6 | import { cn } from '@/lib/utils';
7 |
8 | const ToastProvider = ToastPrimitives.Provider;
9 |
10 | const ToastViewport = React.forwardRef<
11 | React.ElementRef,
12 | React.ComponentPropsWithoutRef
13 | >(({ className, ...props }, ref) => (
14 |
22 | ));
23 | ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
24 |
25 | const toastVariants = cva(
26 | 'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
27 | {
28 | variants: {
29 | variant: {
30 | default: 'border bg-background text-foreground',
31 | destructive:
32 | 'destructive group border-destructive bg-destructive text-destructive-foreground',
33 | success: 'border-green-600 bg-green-600 text-white',
34 | },
35 | },
36 | defaultVariants: {
37 | variant: 'default',
38 | },
39 | }
40 | );
41 |
42 | const Toast = React.forwardRef<
43 | React.ElementRef,
44 | React.ComponentPropsWithoutRef &
45 | VariantProps
46 | >(({ className, variant, ...props }, ref) => {
47 | return (
48 |
53 | );
54 | });
55 | Toast.displayName = ToastPrimitives.Root.displayName;
56 |
57 | const ToastAction = React.forwardRef<
58 | React.ElementRef,
59 | React.ComponentPropsWithoutRef
60 | >(({ className, ...props }, ref) => (
61 |
69 | ));
70 | ToastAction.displayName = ToastPrimitives.Action.displayName;
71 |
72 | const ToastClose = React.forwardRef<
73 | React.ElementRef,
74 | React.ComponentPropsWithoutRef
75 | >(({ className, ...props }, ref) => (
76 |
85 |
86 |
87 | ));
88 | ToastClose.displayName = ToastPrimitives.Close.displayName;
89 |
90 | const ToastTitle = React.forwardRef<
91 | React.ElementRef,
92 | React.ComponentPropsWithoutRef
93 | >(({ className, ...props }, ref) => (
94 |
99 | ));
100 | ToastTitle.displayName = ToastPrimitives.Title.displayName;
101 |
102 | const ToastDescription = React.forwardRef<
103 | React.ElementRef,
104 | React.ComponentPropsWithoutRef
105 | >(({ className, ...props }, ref) => (
106 |
111 | ));
112 | ToastDescription.displayName = ToastPrimitives.Description.displayName;
113 |
114 | type ToastProps = React.ComponentPropsWithoutRef;
115 |
116 | type ToastActionElement = React.ReactElement;
117 |
118 | export {
119 | type ToastProps,
120 | type ToastActionElement,
121 | ToastProvider,
122 | ToastViewport,
123 | Toast,
124 | ToastTitle,
125 | ToastDescription,
126 | ToastClose,
127 | ToastAction,
128 | };
129 |
--------------------------------------------------------------------------------
/src/components/ui/toaster.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Toast,
3 | ToastClose,
4 | ToastDescription,
5 | ToastProvider,
6 | ToastTitle,
7 | ToastViewport,
8 | } from '@/components/ui/toast';
9 | import { useToast } from '@/components/ui/use-toast';
10 |
11 | export function Toaster() {
12 | const { toasts } = useToast();
13 |
14 | return (
15 |
16 | {toasts.map(function ({ id, title, description, action, ...props }) {
17 | return (
18 |
19 |
20 | {title && {title}}
21 | {description && (
22 | {description}
23 | )}
24 |
25 | {action}
26 |
27 |
28 | );
29 | })}
30 |
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/ui/tooltip.tsx:
--------------------------------------------------------------------------------
1 | import * as TooltipPrimitive from '@radix-ui/react-tooltip';
2 | import * as React from 'react';
3 |
4 | import { cn } from '@/lib/utils';
5 |
6 | const TooltipProvider = TooltipPrimitive.Provider;
7 |
8 | const Tooltip = TooltipPrimitive.Root;
9 |
10 | const TooltipTrigger = TooltipPrimitive.Trigger;
11 |
12 | const TooltipContent = React.forwardRef<
13 | HTMLDivElement,
14 | React.ComponentPropsWithoutRef
15 | >((props, ref) => {
16 | const { className, sideOffset = 4, ...rest } = props;
17 | return (
18 |
28 | );
29 | });
30 |
31 | TooltipContent.displayName = TooltipPrimitive.Content.displayName;
32 |
33 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
34 |
--------------------------------------------------------------------------------
/src/components/ui/use-toast.ts:
--------------------------------------------------------------------------------
1 | // Inspired by react-hot-toast library
2 | import * as React from 'react';
3 |
4 | import type { ToastActionElement, ToastProps } from '@/components/ui/toast';
5 |
6 | const TOAST_LIMIT = 1;
7 | const TOAST_REMOVE_DELAY = 1000000;
8 |
9 | type ToasterToast = ToastProps & {
10 | id: string;
11 | title?: React.ReactNode;
12 | description?: React.ReactNode;
13 | action?: ToastActionElement;
14 | };
15 |
16 | const actionTypes = {
17 | ADD_TOAST: 'ADD_TOAST',
18 | UPDATE_TOAST: 'UPDATE_TOAST',
19 | DISMISS_TOAST: 'DISMISS_TOAST',
20 | REMOVE_TOAST: 'REMOVE_TOAST',
21 | } as const;
22 |
23 | let count = 0;
24 |
25 | function genId() {
26 | count = (count + 1) % Number.MAX_SAFE_INTEGER;
27 | return count.toString();
28 | }
29 |
30 | type ActionType = typeof actionTypes;
31 |
32 | type Action =
33 | | {
34 | type: ActionType['ADD_TOAST'];
35 | toast: ToasterToast;
36 | }
37 | | {
38 | type: ActionType['UPDATE_TOAST'];
39 | toast: Partial;
40 | }
41 | | {
42 | type: ActionType['DISMISS_TOAST'];
43 | toastId?: ToasterToast['id'];
44 | }
45 | | {
46 | type: ActionType['REMOVE_TOAST'];
47 | toastId?: ToasterToast['id'];
48 | };
49 |
50 | interface State {
51 | toasts: ToasterToast[];
52 | }
53 |
54 | const toastTimeouts = new Map>();
55 |
56 | const addToRemoveQueue = (toastId: string) => {
57 | if (toastTimeouts.has(toastId)) {
58 | return;
59 | }
60 |
61 | const timeout = setTimeout(() => {
62 | toastTimeouts.delete(toastId);
63 | dispatch({
64 | type: 'REMOVE_TOAST',
65 | toastId: toastId,
66 | });
67 | }, TOAST_REMOVE_DELAY);
68 |
69 | toastTimeouts.set(toastId, timeout);
70 | };
71 |
72 | export const reducer = (state: State, action: Action): State => {
73 | switch (action.type) {
74 | case 'ADD_TOAST':
75 | return {
76 | ...state,
77 | toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
78 | };
79 |
80 | case 'UPDATE_TOAST':
81 | return {
82 | ...state,
83 | toasts: state.toasts.map((t) =>
84 | t.id === action.toast.id ? { ...t, ...action.toast } : t
85 | ),
86 | };
87 |
88 | case 'DISMISS_TOAST': {
89 | const { toastId } = action;
90 |
91 | // ! Side effects ! - This could be extracted into a dismissToast() action,
92 | // but I'll keep it here for simplicity
93 | if (toastId) {
94 | addToRemoveQueue(toastId);
95 | } else {
96 | state.toasts.forEach((toast) => {
97 | addToRemoveQueue(toast.id);
98 | });
99 | }
100 |
101 | return {
102 | ...state,
103 | toasts: state.toasts.map((t) =>
104 | t.id === toastId || toastId === undefined
105 | ? {
106 | ...t,
107 | open: false,
108 | }
109 | : t
110 | ),
111 | };
112 | }
113 | case 'REMOVE_TOAST':
114 | if (action.toastId === undefined) {
115 | return {
116 | ...state,
117 | toasts: [],
118 | };
119 | }
120 | return {
121 | ...state,
122 | toasts: state.toasts.filter((t) => t.id !== action.toastId),
123 | };
124 | }
125 | };
126 |
127 | const listeners: Array<(state: State) => void> = [];
128 |
129 | let memoryState: State = { toasts: [] };
130 |
131 | function dispatch(action: Action) {
132 | memoryState = reducer(memoryState, action);
133 | listeners.forEach((listener) => {
134 | listener(memoryState);
135 | });
136 | }
137 |
138 | type Toast = Omit;
139 |
140 | function toast({ ...props }: Toast) {
141 | const id = genId();
142 |
143 | const update = (props: ToasterToast) =>
144 | dispatch({
145 | type: 'UPDATE_TOAST',
146 | toast: { ...props, id },
147 | });
148 | const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id });
149 |
150 | dispatch({
151 | type: 'ADD_TOAST',
152 | toast: {
153 | ...props,
154 | id,
155 | open: true,
156 | onOpenChange: (open) => {
157 | if (!open) dismiss();
158 | },
159 | },
160 | });
161 |
162 | return {
163 | id: id,
164 | dismiss,
165 | update,
166 | };
167 | }
168 |
169 | function useToast() {
170 | const [state, setState] = React.useState(memoryState);
171 |
172 | React.useEffect(() => {
173 | listeners.push(setState);
174 | return () => {
175 | const index = listeners.indexOf(setState);
176 | if (index > -1) {
177 | listeners.splice(index, 1);
178 | }
179 | };
180 | }, [state]);
181 |
182 | return {
183 | ...state,
184 | toast,
185 | dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
186 | };
187 | }
188 |
189 | export { useToast, toast };
190 |
--------------------------------------------------------------------------------
/src/components/us-abbr.json:
--------------------------------------------------------------------------------
1 | {
2 | "01": "AL",
3 | "02": "AK",
4 | "60": "AS",
5 | "04": "AZ",
6 | "05": "AR",
7 | "06": "CA",
8 | "08": "CO",
9 | "09": "CT",
10 | "10": "DE",
11 | "12": "FL",
12 | "64": "FM",
13 | "13": "GA",
14 | "66": "GU",
15 | "15": "HI",
16 | "16": "ID",
17 | "17": "IL",
18 | "18": "IN",
19 | "19": "IA",
20 | "20": "KS",
21 | "21": "KY",
22 | "22": "LA",
23 | "23": "ME",
24 | "68": "MH",
25 | "24": "MD",
26 | "25": "MA",
27 | "26": "MI",
28 | "27": "MN",
29 | "28": "MS",
30 | "29": "MO",
31 | "30": "MT",
32 | "31": "NE",
33 | "32": "NV",
34 | "33": "NH",
35 | "34": "NJ",
36 | "35": "NM",
37 | "36": "NY",
38 | "37": "NC",
39 | "38": "ND",
40 | "69": "MP",
41 | "39": "OH",
42 | "40": "OK",
43 | "41": "OR",
44 | "70": "PW",
45 | "42": "PA",
46 | "72": "PR",
47 | "44": "RI",
48 | "45": "SC",
49 | "46": "SD",
50 | "47": "TN",
51 | "48": "TX",
52 | "74": "UM",
53 | "49": "UT",
54 | "50": "VT",
55 | "51": "VA",
56 | "78": "VI",
57 | "53": "WA",
58 | "54": "WV",
59 | "55": "WI",
60 | "56": "WY"
61 | }
62 |
--------------------------------------------------------------------------------
/src/config/brandingConfig.ts:
--------------------------------------------------------------------------------
1 | // src/config/brandingConfig.ts
2 |
3 | import brandingOverride from './brandingOverride';
4 |
5 | // Define default branding configuration
6 | const defaultConfig = {
7 | companyName: 'EmergentAGI Inc.',
8 | deploymentName: 'R2R',
9 | socialLinks: {
10 | twitter: { enabled: true, url: 'https://twitter.com/ocolegro?lang=en' },
11 | github: { enabled: true, url: 'https://github.com/SciPhi-AI/R2R' },
12 | discord: { enabled: true, url: 'https://discord.gg/p6KqD2kjtB' },
13 | },
14 | navbar: {
15 | appName: 'SciPhi',
16 | showDocsButton: true,
17 | menuItems: {
18 | home: true,
19 | documents: true,
20 | collections: true,
21 | chat: true,
22 | search: true,
23 | users: true,
24 | logs: true,
25 | analytics: false,
26 | settings: true,
27 | },
28 | },
29 | logo: {
30 | src: '/images/sciphi.svg',
31 | alt: 'sciphi.svg',
32 | },
33 | theme: 'dark',
34 | homePage: {
35 | pythonSdk: true,
36 | githubCard: true,
37 | hatchetCard: true,
38 | },
39 | nextConfig: {
40 | additionalRemoteDomain: '',
41 | },
42 | };
43 |
44 | // ✅ Declare `window.__BRANDING_CONFIG__` globally to avoid TypeScript errors
45 | declare global {
46 | interface Window {
47 | __BRANDING_CONFIG__?: Partial;
48 | }
49 | }
50 |
51 | // ✅ Load user-defined config from `window.__BRANDING_CONFIG__` (if available)
52 | const userConfig =
53 | (typeof window !== 'undefined' && window.__BRANDING_CONFIG__) || {};
54 |
55 | // ✅ Merge `defaultConfig`, `brandingOverride.ts`, and `userConfig`
56 | export const brandingConfig = {
57 | ...defaultConfig,
58 | ...brandingOverride,
59 | ...userConfig,
60 | };
61 |
--------------------------------------------------------------------------------
/src/config/brandingOverride.ts:
--------------------------------------------------------------------------------
1 | // src/config/brandingOverride.ts
2 |
3 | // This file allows overriding branding configurations.
4 | // If no override is provided, defaults from `brandingConfig.ts` will be used.
5 |
6 | const brandingOverride = {
7 | // Example overrides:
8 | // companyName: 'Example Company Name',
9 | // deploymentName: 'RAG Server 5',
10 | // navbar: {
11 | // appName: 'MyApp',
12 | // showDocsButton: false,
13 | // menuItems: {
14 | // home: true,
15 | // documents: true,
16 | // collections: true,
17 | // chat: true,
18 | // search: true,
19 | // users: true,
20 | // logs: true,
21 | // analytics: true,
22 | // settings: true,
23 | // },
24 | //},
25 | //logo: {
26 | // src: 'https://example.com/logo.png',
27 | // alt: 'https://example.com/logo.png',
28 | //},
29 | //theme: 'light',
30 | //homePage: {
31 | // pythonSdk: false,
32 | // githubCard: false,
33 | // hatchetCard: false,
34 | //},
35 | //nextConfig: {
36 | // additionalRemoteDomain: 'cleverthis.com',
37 | //},
38 | };
39 |
40 | // Export the override object
41 | export default brandingOverride;
42 |
--------------------------------------------------------------------------------
/src/hooks/usePagination.ts:
--------------------------------------------------------------------------------
1 | // hooks/usePagination.ts
2 | import { useState, useCallback, useEffect } from 'react';
3 |
4 | interface UsePaginationProps {
5 | fetchData: (
6 | offset: number,
7 | limit: number
8 | ) => Promise<{ results: T[]; totalEntries: number }>;
9 | pageSize: number;
10 | initialPage?: number;
11 | key?: string;
12 | initialPrefetchPages?: number;
13 | prefetchThreshold?: number;
14 | }
15 |
16 | function usePagination({
17 | fetchData,
18 | pageSize,
19 | initialPage = 1,
20 | key,
21 | initialPrefetchPages = 0,
22 | prefetchThreshold = 0,
23 | }: UsePaginationProps) {
24 | const [currentPage, setCurrentPage] = useState(initialPage);
25 | const [totalItems, setTotalItems] = useState(0);
26 | const [data, setData] = useState([]);
27 | const [loading, setLoading] = useState(false);
28 |
29 | const loadPage = useCallback(
30 | async (page: number) => {
31 | setLoading(true);
32 | try {
33 | const offset = (page - 1) * pageSize;
34 | const { results, totalEntries } = await fetchData(offset, pageSize);
35 | setData(results);
36 | setTotalItems(totalEntries);
37 | setCurrentPage(page);
38 | } catch (error) {
39 | console.error(`Error fetching data for page ${page}:`, error);
40 | } finally {
41 | setLoading(false);
42 | }
43 | },
44 | [fetchData, pageSize]
45 | );
46 |
47 | useEffect(() => {
48 | loadPage(initialPage);
49 | }, [initialPage, loadPage]);
50 |
51 | const goToPage = useCallback(
52 | (page: number) => {
53 | loadPage(page);
54 | },
55 | [loadPage]
56 | );
57 |
58 | return {
59 | data,
60 | currentPage,
61 | totalPages: Math.max(1, Math.ceil(totalItems / pageSize)),
62 | totalItems,
63 | loading,
64 | goToPage,
65 | };
66 | }
67 |
68 | export default usePagination;
69 |
--------------------------------------------------------------------------------
/src/instrumentation.ts:
--------------------------------------------------------------------------------
1 | import * as Sentry from '@sentry/nextjs';
2 |
3 | export async function register() {
4 | if (process.env.NEXT_RUNTIME === 'nodejs') {
5 | await import('../sentry.server.config');
6 | }
7 |
8 | if (process.env.NEXT_RUNTIME === 'edge') {
9 | await import('../sentry.edge.config');
10 | }
11 | }
12 |
13 | export const onRequestError = Sentry.captureRequestError;
14 |
--------------------------------------------------------------------------------
/src/lib/CustomErrors.ts:
--------------------------------------------------------------------------------
1 | export class NetworkError extends Error {
2 | constructor(message: string) {
3 | super(message);
4 | this.name = 'NetworkError';
5 | }
6 | }
7 |
8 | export class AuthenticationError extends Error {
9 | constructor(message: string) {
10 | super(message);
11 | this.name = 'AuthenticationError';
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/lib/debounce.ts:
--------------------------------------------------------------------------------
1 | export default function debounce any>(
2 | func: F,
3 | waitFor: number
4 | ) {
5 | let timeout: ReturnType | null = null;
6 |
7 | return (...args: Parameters): void => {
8 | if (timeout !== null) {
9 | clearTimeout(timeout);
10 | }
11 | timeout = setTimeout(() => func(...args), waitFor);
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/src/lib/posthog-client.tsx:
--------------------------------------------------------------------------------
1 | // Anonymized telemetry data is sent to PostHog to help us improve the product. You can disable this by setting the R2R_DASHBOARD_DISABLE_TELEMETRY environment variable to 'true'.
2 |
3 | import posthog from 'posthog-js';
4 |
5 | const posthogApiKey = 'phc_OPBbibOIErCGc4NDLQsOrMuYFTKDmRwXX6qxnTr6zpU';
6 | const posthogHost = 'https://us.i.posthog.com';
7 |
8 | function initializePostHog() {
9 | if (typeof window === 'undefined') {
10 | return;
11 | }
12 |
13 | posthog.init(posthogApiKey, {
14 | api_host: posthogHost,
15 | autocapture: true,
16 | });
17 |
18 | if (window.__RUNTIME_CONFIG__?.R2R_DASHBOARD_DISABLE_TELEMETRY === 'true') {
19 | posthog.opt_out_capturing();
20 | }
21 | }
22 |
23 | export default posthog;
24 | export { initializePostHog };
25 |
--------------------------------------------------------------------------------
/src/lib/remToPx.ts:
--------------------------------------------------------------------------------
1 | export function remToPx(remValue: number) {
2 | const rootFontSize =
3 | typeof window === 'undefined'
4 | ? 16
5 | : parseFloat(window.getComputedStyle(document.documentElement).fontSize);
6 |
7 | return remValue * rootFontSize;
8 | }
9 |
--------------------------------------------------------------------------------
/src/lib/supabase.ts:
--------------------------------------------------------------------------------
1 | // lib/supabase.ts
2 |
3 | import { createClient, SupabaseClient } from '@supabase/supabase-js';
4 |
5 | let supabase: SupabaseClient | null = null;
6 |
7 | export const getSupabase = (): SupabaseClient | null => {
8 | if (typeof window !== 'undefined' && window.__RUNTIME_CONFIG__) {
9 | if (!supabase) {
10 | const supabaseUrl = window.__RUNTIME_CONFIG__.SUPABASE_URL;
11 | const supabaseAnonKey = window.__RUNTIME_CONFIG__.SUPABASE_ANON_KEY;
12 | if (supabaseUrl && supabaseAnonKey) {
13 | supabase = createClient(supabaseUrl, supabaseAnonKey);
14 | } else {
15 | console.warn('Supabase URL or Anon Key is missing in runtime config.');
16 | }
17 | }
18 | return supabase;
19 | }
20 | return null;
21 | };
22 |
23 | export { supabase };
24 |
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from 'clsx';
2 | import { useState, useEffect } from 'react';
3 | import { twMerge } from 'tailwind-merge';
4 | import { v5 as uuidv5 } from 'uuid';
5 |
6 | export const setColor = (keyword: string): string => {
7 | switch (keyword) {
8 | case 'success':
9 | return 'bg-emerald-400';
10 | case 'failed':
11 | return 'bg-red-400';
12 | case 'Search':
13 | return 'bg-sky-500';
14 | case 'Embedding':
15 | return 'bg-orange-600';
16 | case 'WARNING':
17 | return 'bg-amber-400';
18 | default:
19 | return 'bg-gray-400';
20 | }
21 | };
22 |
23 | export const setTextColor = (keyword: string): string => {
24 | switch (keyword) {
25 | case 'WARNING':
26 | return 'text-amber-800';
27 | default:
28 | return 'text-gray-800';
29 | }
30 | };
31 |
32 | export const isValidUrl = (value: string) => {
33 | const urlPattern = new RegExp(
34 | '^https?:\\/\\/' + // must start with http:// or https://
35 | '((([a-zA-Z0-9-_]+\\.)+[a-zA-Z]{2,})|((\\d{1,3}\\.){3}\\d{1,3}))' + // domain name or IP address
36 | '(\\:\\d+)?' + // optional port
37 | '(\\/.*)?' + // optional path
38 | '$' // end of string
39 | );
40 | return urlPattern.test(value);
41 | };
42 |
43 | export const capitalizeFirstLetter = (string: string) => {
44 | if (!string) {
45 | return string;
46 | }
47 | return string.charAt(0).toUpperCase() + string.slice(1);
48 | };
49 |
50 | type ValidateFunction = (value: string) => {
51 | isValid: boolean;
52 | message: string;
53 | };
54 | export const useValidation = (
55 | value: string,
56 | validations: ValidateFunction[]
57 | ) => {
58 | const [isValid, setIsValid] = useState(true);
59 | const [errorMessage, setErrorMessage] = useState('');
60 |
61 | useEffect(() => {
62 | for (const validate of validations) {
63 | const result = validate(value);
64 | if (!result.isValid) {
65 | setIsValid(false);
66 | setErrorMessage(result.message);
67 | return;
68 | }
69 | }
70 | setIsValid(true);
71 | setErrorMessage('');
72 | }, [value, validations]);
73 |
74 | const inputStyles = isValid ? 'border-green-700' : 'border-red-600';
75 |
76 | return { isValid, inputStyles, errorMessage };
77 | };
78 |
79 | export function cn(...inputs: ClassValue[]) {
80 | return twMerge(clsx(inputs));
81 | }
82 |
83 | export function generateIdFromLabel(label: string): string {
84 | const NAMESPACE_DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8'; // UUID for DNS namespace
85 | return uuidv5(label, NAMESPACE_DNS);
86 | }
87 |
88 | export function formatFileSize(bytes: number | undefined): string {
89 | if (bytes === undefined || isNaN(bytes)) {
90 | return 'N/A';
91 | }
92 |
93 | const units = ['B', 'KB', 'MB', 'GB', 'TB'];
94 | let size = bytes;
95 | let unitIndex = 0;
96 |
97 | while (size >= 1024 && unitIndex < units.length - 1) {
98 | size /= 1024;
99 | unitIndex++;
100 | }
101 |
102 | return `${size.toFixed(2)} ${units[unitIndex]}`;
103 | }
104 |
--------------------------------------------------------------------------------
/src/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import type { AppProps } from 'next/app';
2 | import { useRouter } from 'next/router';
3 | import { useTheme } from 'next-themes';
4 | import { useEffect, useCallback } from 'react';
5 |
6 | import { ThemeProvider } from '@/components/ThemeProvider';
7 | import { brandingConfig } from '@/config/brandingConfig';
8 | import { UserProvider, useUserContext } from '@/context/UserContext';
9 | import '@/styles/globals.css';
10 | import { initializePostHog } from '@/lib/posthog-client';
11 |
12 | function MyAppContent({ Component, pageProps }: AppProps) {
13 | const { setTheme } = useTheme();
14 | const { isAuthenticated, isSuperUser, authState } = useUserContext();
15 | const router = useRouter();
16 |
17 | useEffect(() => {
18 | setTheme(brandingConfig.theme);
19 | initializePostHog();
20 | }, []);
21 |
22 | const checkAccess = useCallback(async () => {
23 | const publicRoutes = ['/auth/login', '/auth/signup'];
24 | const userRoutes = [
25 | '/documents',
26 | '/collections',
27 | '/collection',
28 | '/chat',
29 | '/account',
30 | ];
31 | const currentPath = router.pathname;
32 |
33 | const isUserRoute = (path: string) => {
34 | return userRoutes.some((route) => path.startsWith(route));
35 | };
36 |
37 | if (!isAuthenticated) {
38 | if (!publicRoutes.includes(currentPath)) {
39 | router.replace('/auth/login');
40 | }
41 | return;
42 | }
43 |
44 | if (isSuperUser()) {
45 | return;
46 | }
47 |
48 | if (!isUserRoute(currentPath)) {
49 | router.replace('/documents');
50 | }
51 | }, [isAuthenticated, isSuperUser, authState.userRole, router]);
52 |
53 | useEffect(() => {
54 | checkAccess();
55 | }, [checkAccess]);
56 |
57 | return ;
58 | }
59 |
60 | function MyApp(props: AppProps) {
61 | // Move the runtime config check into useEffect
62 | useEffect(() => {
63 | // Load the env-config.js script dynamically
64 | const script = document.createElement('script');
65 | script.src = '/env-config.js';
66 | script.onload = () => {
67 | if (typeof window !== 'undefined' && window.__RUNTIME_CONFIG__) {
68 | console.log('Runtime Config:', window.__RUNTIME_CONFIG__);
69 | } else {
70 | console.warn('Runtime Config not found!');
71 | }
72 | };
73 | document.body.appendChild(script);
74 | }, []);
75 |
76 | return (
77 |
83 |
84 |
85 |
86 |
87 | );
88 | }
89 |
90 | export default MyApp;
91 |
--------------------------------------------------------------------------------
/src/pages/_error.jsx:
--------------------------------------------------------------------------------
1 | import * as Sentry from '@sentry/nextjs';
2 | import Error from 'next/error';
3 |
4 | const CustomErrorComponent = (props) => {
5 | return ;
6 | };
7 |
8 | CustomErrorComponent.getInitialProps = async (contextData) => {
9 | // In case this is running in a serverless function, await this in order to give Sentry
10 | // time to send the error before the lambda exits
11 | await Sentry.captureUnderscoreErrorException(contextData);
12 |
13 | // This will contain the status code of the response
14 | return Error.getInitialProps(contextData);
15 | };
16 |
17 | export default CustomErrorComponent;
18 |
--------------------------------------------------------------------------------
/src/pages/analytics.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useCallback } from 'react';
2 |
3 | import AverageScoreCard from '@/components/AverageScoreCard';
4 | import DAUCard from '@/components/DAUCard';
5 | import ErrorsCard from '@/components/ErrorsCard';
6 | import Layout from '@/components/Layout';
7 | import LLMCompletionCard from '@/components/LLMCompletionCard';
8 | import RequestsCard from '@/components/RequestsCard';
9 | import USMapCard from '@/components/USMapCard';
10 | import WAUCard from '@/components/WAUCard';
11 | import WorldMapCard from '@/components/WorldMapCard';
12 | import { useUserContext } from '@/context/UserContext';
13 |
14 | const processLogData = (data: any) => {
15 | const now = new Date();
16 | const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
17 |
18 | if (!data || !Array.isArray(data.results) || data.results.length === 0) {
19 | console.error('Invalid data structure:', data);
20 | return [];
21 | }
22 |
23 | const { entries } = data.results[0].entries;
24 |
25 | if (!Array.isArray(entries)) {
26 | console.error('Invalid entries structure:', entries);
27 | return [];
28 | }
29 |
30 | return entries
31 | .filter((entry: any) => entry && entry.key === 'completion_record')
32 | .map((entry: any) => {
33 | try {
34 | const value = JSON.parse(entry.value);
35 | return {
36 | date: new Date(value.timestamp),
37 | score: Array.isArray(value.score) ? value.score[0] : value.score,
38 | };
39 | } catch (error) {
40 | console.error('Error parsing entry:', error, entry);
41 | return null;
42 | }
43 | })
44 | .filter(
45 | (entry): entry is { date: Date; score: number } =>
46 | entry !== null &&
47 | !isNaN(entry.date.getTime()) &&
48 | typeof entry.score === 'number'
49 | )
50 | .filter((entry) => entry.date >= sevenDaysAgo && entry.date <= now)
51 | .sort((a, b) => a.date.getTime() - b.date.getTime());
52 | };
53 |
54 | const Analytics: React.FC = () => {
55 | const { getClient } = useUserContext();
56 | const [scoreData, setScoreData] = useState<
57 | Array<{ date: Date; score: number }>
58 | >([]);
59 |
60 | const fetchLogs = useCallback(async () => {
61 | try {
62 | const client = await getClient();
63 | if (!client) {
64 | throw new Error('Failed to get authenticated client');
65 | }
66 |
67 | const data = {};
68 | const processedData = processLogData(data);
69 | setScoreData(processedData);
70 | } catch (error) {
71 | console.error('Error fetching logs:', error);
72 | }
73 | }, [getClient]);
74 |
75 | useEffect(() => {
76 | fetchLogs();
77 | }, [fetchLogs]);
78 |
79 | return (
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | );
113 | };
114 |
115 | export default Analytics;
116 |
--------------------------------------------------------------------------------
/src/pages/api/sentry-example-api.js:
--------------------------------------------------------------------------------
1 | // A faulty API route to test Sentry's error monitoring
2 | export default function handler(_req, res) {
3 | throw new Error('Sentry Example API Route Error');
4 | res.status(200).json({ name: 'John Doe' });
5 | }
6 |
--------------------------------------------------------------------------------
/src/pages/error.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import React from 'react';
3 |
4 | export default function ErrorPage() {
5 | return (
6 |
7 |
8 | 404
9 |
10 |
11 | Page Not Found
12 |
13 |
24 |
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/src/styles/Index.module.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | width: 100%;
3 | position: relative;
4 | }
5 |
6 | .topBar {
7 | background-color: bg-color2;
8 | padding: 1.5rem 12rem 1.2rem 12rem;
9 | background-color: #262a2d;
10 | // background-color: var(--schiphi-top-bg);
11 | }
12 |
13 | .main {
14 | display: flex;
15 | flex-direction: column;
16 | min-height: calc(100vh - 122.5px);
17 | margin: 6rem 14rem 0 14rem;
18 |
19 | .gridView {
20 | margin-top: 1.5rem;
21 | display: grid;
22 | grid-template-columns: repeat(3, 1fr);
23 | grid-auto-rows: auto;
24 | column-gap: 1.5rem;
25 | row-gap: 1rem;
26 | }
27 |
28 | .column {
29 | grid-template-columns: 1fr;
30 | }
31 | }
32 |
33 | .fullWidthTable {
34 | width: 100%;
35 | }
36 |
37 | .footer {
38 | display: flex;
39 | flex-direction: column;
40 | margin: 4rem 14rem 0 14rem;
41 | }
42 | .datasetHeaderRightAlign {
43 | margin-top: 1.5rem;
44 | margin-bottom: 1.5rem;
45 |
46 | display: flex;
47 | justify-content: flex-end;
48 | /* Align items to the right */
49 | width: 100%;
50 | /* Ensures the flex container takes up full width */
51 | }
52 |
--------------------------------------------------------------------------------
/startup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Function to replace placeholders with environment variables
4 | replace_env_vars() {
5 | echo "Replacing environment variables in env-config.js..."
6 | sed -i "s|__NEXT_PUBLIC_R2R_DEPLOYMENT_URL__|${NEXT_PUBLIC_R2R_DEPLOYMENT_URL}|g" /app/public/env-config.js
7 | sed -i "s|__NEXT_PUBLIC_R2R_DEFAULT_EMAIL__|${NEXT_PUBLIC_R2R_DEFAULT_EMAIL}|g" /app/public/env-config.js
8 | sed -i "s|__NEXT_PUBLIC_R2R_DEFAULT_PASSWORD__|${NEXT_PUBLIC_R2R_DEFAULT_PASSWORD}|g" /app/public/env-config.js
9 | sed -i "s|__R2R_DASHBOARD_DISABLE_TELEMETRY__|${R2R_DASHBOARD_DISABLE_TELEMETRY}|g" /app/public/env-config.js
10 | sed -i "s|__SUPABASE_URL__|${SUPABASE_URL}|g" /app/public/env-config.js
11 | sed -i "s|__SUPABASE_ANON_KEY__|${SUPABASE_ANON_KEY}|g" /app/public/env-config.js
12 | sed -i "s|__NEXT_PUBLIC_HATCHET_DASHBOARD_URL__|${NEXT_PUBLIC_HATCHET_DASHBOARD_URL}|g" /app/public/env-config.js
13 | sed -i "s|__NEXT_PUBLIC_SENTRY_DSN__|${NEXT_PUBLIC_SENTRY_DSN}|g" /app/public/env-config.js
14 | }
15 |
16 | # Replace environment variables
17 | replace_env_vars
18 |
19 | # Start the Next.js server
20 | exec node server.js
21 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: ['class'],
4 | content: [
5 | './pages/**/*.{ts,tsx}',
6 | './@/components/**/*.{ts,tsx}',
7 | './components/**/*.{ts,tsx}',
8 | './app/**/*.{ts,tsx}',
9 | './src/**/*.{ts,tsx}',
10 | ],
11 | prefix: '',
12 | theme: {
13 | container: {
14 | center: 'true',
15 | padding: '2rem',
16 | screens: {
17 | '2xl': '1400px',
18 | },
19 | },
20 | extend: {
21 | colors: {
22 | accent: {
23 | base: 'var(--accent-base)',
24 | light: 'var(--accent-light)',
25 | lighter: 'var(--accent-lighter)',
26 | dark: 'var(--accent-dark)',
27 | darker: 'var(--accent-darker)',
28 | contrast: 'var(--accent-contrast)',
29 | },
30 | color1: 'var(--color-1)',
31 | color2: 'var(--color-2)',
32 | color3: 'var(--color-3)',
33 | color4: 'var(--color-4)',
34 | color5: 'var(--color-5)',
35 | color6: 'var(--color-6)',
36 | color7: 'var(--color-7)',
37 | color8: 'var(--color-8)',
38 | color9: 'var(--color-9)',
39 | border: 'var(--border)',
40 | input: 'var(--input)',
41 | ring: 'var(--ring)',
42 | background: 'var(--background)',
43 | foreground: 'var(--foreground)',
44 | primary: {
45 | DEFAULT: 'var(--primary)',
46 | foreground: 'var(--primary-foreground)',
47 | },
48 | secondary: {
49 | DEFAULT: 'var(--secondary)',
50 | foreground: 'var(--secondary-foreground)',
51 | },
52 | destructive: {
53 | DEFAULT: 'var(--destructive)',
54 | foreground: 'var(--destructive-foreground)',
55 | },
56 | muted: {
57 | DEFAULT: 'var(--muted)',
58 | foreground: 'var(--muted-foreground)',
59 | },
60 | popover: {
61 | DEFAULT: 'var(--popover)',
62 | foreground: 'var(--popover-foreground)',
63 | },
64 | card: {
65 | DEFAULT: 'var(--card)',
66 | foreground: 'var(--card-foreground)',
67 | },
68 | link: 'var(--link)',
69 | 'link-hover': 'var(--link-hover)',
70 | sidebar: {
71 | DEFAULT: 'hsl(var(--sidebar-background))',
72 | foreground: 'hsl(var(--sidebar-foreground))',
73 | primary: 'hsl(var(--sidebar-primary))',
74 | 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
75 | accent: 'hsl(var(--sidebar-accent))',
76 | 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
77 | border: 'hsl(var(--sidebar-border))',
78 | ring: 'hsl(var(--sidebar-ring))',
79 | },
80 | },
81 | backgroundColor: {
82 | 'primary-custom': 'var(--sciphi-primary)',
83 | 'secondary-custom': 'var(--sciphi-secondary)',
84 | 'accent-custom': 'var(--sciphi-accent)',
85 | },
86 | textColor: {
87 | link: 'var(--link)',
88 | 'link-hover': 'var(--link-hover)',
89 | },
90 | borderRadius: {
91 | lg: 'var(--radius)',
92 | md: 'calc(var(--radius) - 2px)',
93 | sm: 'calc(var(--radius) - 4px)',
94 | },
95 | keyframes: {
96 | 'accordion-down': {
97 | from: {
98 | height: '0',
99 | },
100 | to: {
101 | height: 'var(--radix-accordion-content-height)',
102 | },
103 | },
104 | 'accordion-up': {
105 | from: {
106 | height: 'var(--radix-accordion-content-height)',
107 | },
108 | to: {
109 | height: '0',
110 | },
111 | },
112 | },
113 | animation: {
114 | 'accordion-down': 'accordion-down 0.2s ease-out',
115 | 'accordion-up': 'accordion-up 0.2s ease-out',
116 | },
117 | boxShadow: {
118 | header: 'var(--header-box-shadow)',
119 | shadow: 'var(--shadow)',
120 | 'shadow-hover': 'var(--shadow-hover)',
121 | },
122 | },
123 | },
124 | plugins: [require('tailwindcss-animate'), require('@tailwindcss/forms')],
125 | };
126 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2018",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "experimentalDecorators": true,
7 | "skipLibCheck": true,
8 | "strict": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "noEmit": true,
11 | "esModuleInterop": true,
12 | "module": "esnext",
13 | "moduleResolution": "node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "jsx": "preserve",
17 | "incremental": true,
18 | "baseUrl": ".",
19 | "paths": {
20 | "@/*": ["src/*"],
21 | "@/utils/*": ["./src/utils/*"],
22 | "react": ["./node_modules/@types/react"]
23 | },
24 | "plugins": [
25 | {
26 | "name": "next"
27 | }
28 | ]
29 | },
30 | "typeRoots": ["./node_modules/@types", "./types"],
31 | "include": [
32 | "next-env.d.ts",
33 | "**/*.ts",
34 | "**/*.tsx",
35 | ".next/types/**/*.ts",
36 | "types/**/*.d.ts"
37 | ],
38 | "exclude": ["node_modules", "cloud-docs"]
39 | }
40 |
--------------------------------------------------------------------------------
/types/globals.d.ts:
--------------------------------------------------------------------------------
1 | // types/globals.d.ts
2 | declare global {
3 | interface Window {
4 | __RUNTIME_CONFIG__: {
5 | NEXT_PUBLIC_R2R_DEPLOYMENT_URL: string;
6 | NEXT_PUBLIC_R2R_DEFAULT_EMAIL: string;
7 | NEXT_PUBLIC_R2R_DEFAULT_PASSWORD: string;
8 | R2R_DASHBOARD_DISABLE_TELEMETRY: string;
9 | SUPABASE_URL: string;
10 | SUPABASE_ANON_KEY: string;
11 | NEXT_PUBLIC_HATCHET_DASHBOARD_URL: string;
12 | };
13 | }
14 | }
15 |
16 | export {};
17 |
--------------------------------------------------------------------------------