├── LICENSE ├── README.md ├── nyro-chatbot ├── .env.local.example ├── .eslintrc.json ├── .gitignore ├── .husky │ └── pre-commit ├── .nvmrc ├── .vscode │ └── settings.json ├── __tests__ │ ├── lib │ │ └── openapi-conversion.test.ts │ └── playwright-test │ │ ├── .gitignore │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── playwright.config.ts │ │ └── tests │ │ └── login.spec.ts ├── app │ ├── [locale] │ │ ├── [workspaceid] │ │ │ ├── chat │ │ │ │ ├── [chatid] │ │ │ │ │ └── page.tsx │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── globals.css │ │ ├── help │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── loading.tsx │ │ ├── login │ │ │ ├── page.tsx │ │ │ └── password │ │ │ │ └── page.tsx │ │ ├── page.tsx │ │ └── setup │ │ │ └── page.tsx │ ├── api │ │ ├── assistants │ │ │ └── openai │ │ │ │ └── route.ts │ │ ├── chat │ │ │ ├── anthropic │ │ │ │ └── route.ts │ │ │ ├── azure │ │ │ │ └── route.ts │ │ │ ├── custom │ │ │ │ └── route.ts │ │ │ ├── google │ │ │ │ └── route.ts │ │ │ ├── groq │ │ │ │ └── route.ts │ │ │ ├── mistral │ │ │ │ └── route.ts │ │ │ ├── openai │ │ │ │ └── route.ts │ │ │ ├── openrouter │ │ │ │ └── route.ts │ │ │ ├── perplexity │ │ │ │ └── route.ts │ │ │ └── tools │ │ │ │ └── route.ts │ │ ├── command │ │ │ └── route.ts │ │ ├── keys │ │ │ └── route.ts │ │ ├── retrieval │ │ │ ├── process │ │ │ │ ├── docx │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ └── retrieve │ │ │ │ └── route.ts │ │ └── username │ │ │ ├── available │ │ │ └── route.ts │ │ │ └── get │ │ │ └── route.ts │ └── auth │ │ └── callback │ │ └── route.ts ├── components.json ├── components │ ├── chat │ │ ├── assistant-picker.tsx │ │ ├── chat-command-input.tsx │ │ ├── chat-files-display.tsx │ │ ├── chat-help.tsx │ │ ├── chat-helpers │ │ │ └── index.ts │ │ ├── chat-hooks │ │ │ ├── use-chat-handler.tsx │ │ │ ├── use-chat-history.tsx │ │ │ ├── use-prompt-and-command.tsx │ │ │ ├── use-scroll.tsx │ │ │ └── use-select-file-handler.tsx │ │ ├── chat-input.tsx │ │ ├── chat-messages.tsx │ │ ├── chat-retrieval-settings.tsx │ │ ├── chat-scroll-buttons.tsx │ │ ├── chat-secondary-buttons.tsx │ │ ├── chat-settings.tsx │ │ ├── chat-ui.tsx │ │ ├── file-picker.tsx │ │ ├── prompt-picker.tsx │ │ ├── quick-setting-option.tsx │ │ ├── quick-settings.tsx │ │ └── tool-picker.tsx │ ├── icons │ │ ├── anthropic-svg.tsx │ │ ├── google-svg.tsx │ │ ├── nyro-svg.tsx │ │ └── openai-svg.tsx │ ├── messages │ │ ├── message-actions.tsx │ │ ├── message-codeblock.tsx │ │ ├── message-markdown-memoized.tsx │ │ ├── message-markdown.tsx │ │ ├── message-replies.tsx │ │ └── message.tsx │ ├── models │ │ ├── model-icon.tsx │ │ ├── model-option.tsx │ │ └── model-select.tsx │ ├── setup │ │ ├── api-step.tsx │ │ ├── finish-step.tsx │ │ ├── profile-step.tsx │ │ └── step-container.tsx │ ├── sidebar │ │ ├── items │ │ │ ├── all │ │ │ │ ├── sidebar-create-item.tsx │ │ │ │ ├── sidebar-delete-item.tsx │ │ │ │ ├── sidebar-display-item.tsx │ │ │ │ └── sidebar-update-item.tsx │ │ │ ├── assistants │ │ │ │ ├── assistant-item.tsx │ │ │ │ ├── assistant-retrieval-select.tsx │ │ │ │ ├── assistant-tool-select.tsx │ │ │ │ └── create-assistant.tsx │ │ │ ├── chat │ │ │ │ ├── chat-item.tsx │ │ │ │ ├── delete-chat.tsx │ │ │ │ └── update-chat.tsx │ │ │ ├── collections │ │ │ │ ├── collection-file-select.tsx │ │ │ │ ├── collection-item.tsx │ │ │ │ └── create-collection.tsx │ │ │ ├── files │ │ │ │ ├── create-file.tsx │ │ │ │ └── file-item.tsx │ │ │ ├── folders │ │ │ │ ├── delete-folder.tsx │ │ │ │ ├── folder-item.tsx │ │ │ │ └── update-folder.tsx │ │ │ ├── models │ │ │ │ ├── create-model.tsx │ │ │ │ └── model-item.tsx │ │ │ ├── presets │ │ │ │ ├── create-preset.tsx │ │ │ │ └── preset-item.tsx │ │ │ ├── prompts │ │ │ │ ├── create-prompt.tsx │ │ │ │ └── prompt-item.tsx │ │ │ └── tools │ │ │ │ ├── create-tool.tsx │ │ │ │ └── tool-item.tsx │ │ ├── sidebar-content.tsx │ │ ├── sidebar-create-buttons.tsx │ │ ├── sidebar-data-list.tsx │ │ ├── sidebar-search.tsx │ │ ├── sidebar-switch-item.tsx │ │ ├── sidebar-switcher.tsx │ │ └── sidebar.tsx │ ├── ui │ │ ├── accordion.tsx │ │ ├── advanced-settings.tsx │ │ ├── alert-dialog.tsx │ │ ├── alert.tsx │ │ ├── aspect-ratio.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── brand.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── card.tsx │ │ ├── chat-app.tsx │ │ ├── chat-settings-form.tsx │ │ ├── checkbox.tsx │ │ ├── collapsible.tsx │ │ ├── command.tsx │ │ ├── context-menu.tsx │ │ ├── dashboard.tsx │ │ ├── dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── file-icon.tsx │ │ ├── file-preview.tsx │ │ ├── form.tsx │ │ ├── hover-card.tsx │ │ ├── image-picker.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── limit-display.tsx │ │ ├── menu-bar.tsx │ │ ├── menubar.tsx │ │ ├── navigation-menu.tsx │ │ ├── peek-bar.tsx │ │ ├── popover.tsx │ │ ├── progress.tsx │ │ ├── radio-group.tsx │ │ ├── screen-loader.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── skeleton.tsx │ │ ├── slider.tsx │ │ ├── sonner.tsx │ │ ├── submit-button.tsx │ │ ├── switch.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea-autosize.tsx │ │ ├── textarea.tsx │ │ ├── toast.tsx │ │ ├── toaster.tsx │ │ ├── toggle-group.tsx │ │ ├── toggle.tsx │ │ ├── tooltip.tsx │ │ ├── use-toast.ts │ │ └── with-tooltip.tsx │ ├── utility │ │ ├── alerts.tsx │ │ ├── announcements.tsx │ │ ├── change-password.tsx │ │ ├── command-k.tsx │ │ ├── drawing-canvas.tsx │ │ ├── global-state.tsx │ │ ├── import.tsx │ │ ├── profile-settings.tsx │ │ ├── providers.tsx │ │ ├── theme-switcher.tsx │ │ ├── translations-provider.tsx │ │ └── workspace-switcher.tsx │ └── workspace │ │ ├── assign-workspaces.tsx │ │ ├── delete-workspace.tsx │ │ └── workspace-settings.tsx ├── context │ └── context.tsx ├── db │ ├── assistant-collections.ts │ ├── assistant-files.ts │ ├── assistant-tools.ts │ ├── assistants.ts │ ├── chat-files.ts │ ├── chats.ts │ ├── collection-files.ts │ ├── collections.ts │ ├── files.ts │ ├── folders.ts │ ├── index.ts │ ├── limits.ts │ ├── message-file-items.ts │ ├── messages.ts │ ├── models.ts │ ├── presets.ts │ ├── profile.ts │ ├── prompts.ts │ ├── storage │ │ ├── assistant-images.ts │ │ ├── files.ts │ │ ├── message-images.ts │ │ ├── profile-images.ts │ │ └── workspace-images.ts │ ├── tools.ts │ └── workspaces.ts ├── i18nConfig.js ├── jest.config.ts ├── lib │ ├── blob-to-b64.ts │ ├── build-prompt.ts │ ├── chat-setting-limits.ts │ ├── consume-stream.ts │ ├── envs.ts │ ├── export-old-data.ts │ ├── generate-local-embedding.ts │ ├── hooks │ │ ├── use-copy-to-clipboard.tsx │ │ └── use-hotkey.tsx │ ├── i18n.ts │ ├── models │ │ ├── fetch-models.ts │ │ └── llm │ │ │ ├── anthropic-llm-list.ts │ │ │ ├── google-llm-list.ts │ │ │ ├── groq-llm-list.ts │ │ │ ├── llm-list.ts │ │ │ ├── mistral-llm-list.ts │ │ │ ├── openai-llm-list.ts │ │ │ └── perplexity-llm-list.ts │ ├── openapi-conversion.ts │ ├── retrieval │ │ └── processing │ │ │ ├── csv.ts │ │ │ ├── docx.ts │ │ │ ├── index.ts │ │ │ ├── json.ts │ │ │ ├── md.ts │ │ │ ├── pdf.ts │ │ │ └── txt.ts │ ├── server │ │ ├── server-chat-helpers.ts │ │ └── server-utils.ts │ ├── supabase │ │ ├── browser-client.ts │ │ ├── client.ts │ │ ├── middleware.ts │ │ └── server.ts │ └── utils.ts ├── middleware.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── prettier.config.cjs ├── public │ ├── DARK_BRAND_LOGO.png │ ├── LIGHT_BRAND_LOGO.png │ ├── favicon.ico │ ├── favicon2.ico │ ├── icon-96x39.png │ ├── locales │ │ ├── de │ │ │ └── translation.json │ │ └── en │ │ │ └── translation.json │ ├── manifest.json │ ├── providers │ │ ├── groq.png │ │ ├── meta.png │ │ ├── mistral.png │ │ └── perplexity.png │ ├── readme │ │ └── screenshot.png │ └── worker-development.js ├── supabase │ ├── .gitignore │ ├── config.toml │ ├── migrations │ │ ├── 20240108234540_setup.sql │ │ ├── 20240108234541_add_profiles.sql │ │ ├── 20240108234542_add_workspaces.sql │ │ ├── 20240108234543_add_folders.sql │ │ ├── 20240108234544_add_files.sql │ │ ├── 20240108234545_add_file_items.sql │ │ ├── 20240108234546_add_presets.sql │ │ ├── 20240108234547_add_assistants.sql │ │ ├── 20240108234548_add_chats.sql │ │ ├── 20240108234549_add_messages.sql │ │ ├── 20240108234550_add_prompts.sql │ │ ├── 20240108234551_add_collections.sql │ │ ├── 20240115135033_add_openrouter.sql │ │ ├── 20240115171510_add_assistant_files.sql │ │ ├── 20240115171524_add_tools.sql │ │ ├── 20240115172125_add_assistant_tools.sql │ │ ├── 20240118224049_add_azure_embeddings.sql │ │ ├── 20240124234424_tool_improvements.sql │ │ ├── 20240125192042_upgrade_openai_models.sql │ │ ├── 20240125194719_add_custom_models.sql │ │ ├── 20240129232644_add_workspace_images.sql │ │ ├── 20240212063532_add_at_assistants.sql │ │ ├── 20240213040255_remove_request_in_body_from_tools.sql │ │ ├── 20240213085646_add_context_length_to_custom_models.sql │ │ └── 20240302004845_add_groq.sql │ ├── seed.sql │ └── types.ts ├── tailwind.config.ts ├── tsconfig.json ├── types │ ├── announcement.ts │ ├── assistant-retrieval-item.ts │ ├── chat-file.tsx │ ├── chat-message.ts │ ├── chat.ts │ ├── collection-file.ts │ ├── content-type.ts │ ├── error-response.ts │ ├── file-item-chunk.ts │ ├── images │ │ ├── assistant-image.ts │ │ ├── message-image.ts │ │ └── workspace-image.ts │ ├── index.ts │ ├── key-type.ts │ ├── llms.ts │ ├── models.ts │ ├── sharing.ts │ ├── sidebar-data.ts │ └── valid-keys.ts └── worker │ └── index.js ├── nyro-electron ├── .gitignore ├── favicon.icns ├── favicon.ico ├── main.js ├── package-lock.json ├── package.json ├── preload.js └── yarn.lock └── script.sh /LICENSE: -------------------------------------------------------------------------------- 1 | # Nyro License 2 | 3 | Copyright (c) 2024-present Nyro/PeerEdu, Inc. 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, and/or sublicense copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | 1. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 2. Commercial use, sale, or distribution of the entire Nyro application or any substantial portion thereof is prohibited without explicit written permission from Nyro/PeerEdu, Inc. 10 | 11 | 3. You may not use the name "Nyro" or any Nyro/PeerEdu, Inc. trademarks to endorse or promote products derived from this Software without specific prior written permission. 12 | 13 | 4. Any distribution of the Software or derivative works must be under the same terms and conditions as this license. 14 | 15 | 5. For commercial use or licensing inquiries, please contact Nyro/PeerEdu, Inc. at [insert contact information]. 16 | 17 | 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. -------------------------------------------------------------------------------- /nyro-chatbot/.env.local.example: -------------------------------------------------------------------------------- 1 | # Supabase Public 2 | NEXT_PUBLIC_SUPABASE_URL= 3 | NEXT_PUBLIC_SUPABASE_ANON_KEY= 4 | 5 | # Supabase Private 6 | SUPABASE_SERVICE_ROLE_KEY= 7 | 8 | # Ollama 9 | NEXT_PUBLIC_OLLAMA_URL=http://localhost:11434 10 | 11 | # API Keys (Optional: Entering an API key here overrides the API keys globally for all users.) 12 | OPENAI_API_KEY= 13 | ANTHROPIC_API_KEY= 14 | GOOGLE_GEMINI_API_KEY= 15 | MISTRAL_API_KEY= 16 | GROQ_API_KEY= 17 | PERPLEXITY_API_KEY= 18 | OPENROUTER_API_KEY= 19 | 20 | # OpenAI API Information 21 | NEXT_PUBLIC_OPENAI_ORGANIZATION_ID= 22 | 23 | # Azure API Information 24 | AZURE_OPENAI_API_KEY= 25 | AZURE_OPENAI_ENDPOINT= 26 | AZURE_GPT_35_TURBO_NAME= 27 | AZURE_GPT_45_VISION_NAME= 28 | AZURE_GPT_45_TURBO_NAME= 29 | AZURE_EMBEDDINGS_NAME= 30 | 31 | # General Configuration (Optional) 32 | EMAIL_DOMAIN_WHITELIST= 33 | EMAIL_WHITELIST= 34 | 35 | # File size limit for uploads in bytes 36 | NEXT_PUBLIC_USER_FILE_SIZE_LIMIT=10485760 -------------------------------------------------------------------------------- /nyro-chatbot/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/eslintrc", 3 | "root": true, 4 | "extends": [ 5 | "next/core-web-vitals", 6 | "prettier", 7 | "plugin:tailwindcss/recommended" 8 | ], 9 | "plugins": ["tailwindcss"], 10 | "rules": { 11 | "tailwindcss/no-custom-classname": "off" 12 | }, 13 | "settings": { 14 | "tailwindcss": { 15 | "callees": ["cn", "cva"], 16 | "config": "tailwind.config.js" 17 | } 18 | }, 19 | "overrides": [ 20 | { 21 | "files": ["*.ts", "*.tsx"], 22 | "parser": "@typescript-eslint/parser" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /nyro-chatbot/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | 39 | .VSCodeCounter 40 | tool-schemas 41 | custom-prompts 42 | 43 | sw.js 44 | sw.js.map 45 | workbox-*.js 46 | workbox-*.js.map -------------------------------------------------------------------------------- /nyro-chatbot/.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | . "$(dirname -- "$0")/_/husky.sh" 4 | 5 | npm run lint:fix && npm run format:write && git add . 6 | -------------------------------------------------------------------------------- /nyro-chatbot/.nvmrc: -------------------------------------------------------------------------------- 1 | v20.11.0 2 | -------------------------------------------------------------------------------- /nyro-chatbot/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /nyro-chatbot/__tests__/playwright-test/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /test-results/ 3 | /playwright-report/ 4 | /blob-report/ 5 | /playwright/.cache/ 6 | -------------------------------------------------------------------------------- /nyro-chatbot/__tests__/playwright-test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playwright-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "integration": "playwright test", 8 | "integration:open": "playwright test --ui", 9 | "integration:codegen": "playwright codegen" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@playwright/test": "^1.41.2", 16 | "@types/node": "^20.11.20" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /nyro-chatbot/__tests__/playwright-test/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test'; 2 | 3 | /** 4 | * Read environment variables from file. 5 | * https://github.com/motdotla/dotenv 6 | */ 7 | // require('dotenv').config(); 8 | 9 | /** 10 | * See https://playwright.dev/docs/test-configuration. 11 | */ 12 | export default defineConfig({ 13 | testDir: './tests', 14 | /* Run tests in files in parallel */ 15 | fullyParallel: true, 16 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 17 | forbidOnly: !!process.env.CI, 18 | /* Retry on CI only */ 19 | retries: process.env.CI ? 2 : 0, 20 | /* Opt out of parallel tests on CI. */ 21 | workers: process.env.CI ? 1 : undefined, 22 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 23 | reporter: 'html', 24 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 25 | use: { 26 | /* Base URL to use in actions like `await page.goto('/')`. */ 27 | // baseURL: 'http://127.0.0.1:3000', 28 | 29 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 30 | trace: 'on-first-retry', 31 | }, 32 | 33 | /* Configure projects for major browsers */ 34 | projects: [ 35 | { 36 | name: 'chromium', 37 | use: { ...devices['Desktop Chrome'] }, 38 | }, 39 | 40 | { 41 | name: 'firefox', 42 | use: { ...devices['Desktop Firefox'] }, 43 | }, 44 | 45 | { 46 | name: 'webkit', 47 | use: { ...devices['Desktop Safari'] }, 48 | }, 49 | 50 | /* Test against mobile viewports. */ 51 | // { 52 | // name: 'Mobile Chrome', 53 | // use: { ...devices['Pixel 5'] }, 54 | // }, 55 | // { 56 | // name: 'Mobile Safari', 57 | // use: { ...devices['iPhone 12'] }, 58 | // }, 59 | 60 | /* Test against branded browsers. */ 61 | // { 62 | // name: 'Microsoft Edge', 63 | // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 64 | // }, 65 | // { 66 | // name: 'Google Chrome', 67 | // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, 68 | // }, 69 | ], 70 | 71 | /* Run your local dev server before starting the tests */ 72 | // webServer: { 73 | // command: 'npm run start', 74 | // url: 'http://127.0.0.1:3000', 75 | // reuseExistingServer: !process.env.CI, 76 | // }, 77 | }); 78 | -------------------------------------------------------------------------------- /nyro-chatbot/__tests__/playwright-test/tests/login.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from '@playwright/test'; 2 | 3 | test('start chatting is displayed', async ({ page }) => { 4 | await page.goto('http://localhost:3000/'); 5 | 6 | //expect the start chatting link to be visible 7 | await expect (page.getByRole('link', { name: 'Start Chatting' })).toBeVisible(); 8 | }); 9 | 10 | test('No password error message', async ({ page }) => { 11 | await page.goto('http://localhost:3000/login'); 12 | //fill in dummy email 13 | await page.getByPlaceholder('you@example.com').fill('dummyemail@gmail.com'); 14 | await page.getByRole('button', { name: 'Login' }).click(); 15 | //wait for netwrok to be idle 16 | await page.waitForLoadState('networkidle'); 17 | //validate that correct message is shown to the user 18 | await expect(page.getByText('Invalid login credentials')).toBeVisible(); 19 | 20 | }); 21 | test('No password for signup', async ({ page }) => { 22 | await page.goto('http://localhost:3000/login'); 23 | 24 | await page.getByPlaceholder('you@example.com').fill('dummyEmail@Gmail.com'); 25 | await page.getByRole('button', { name: 'Sign Up' }).click(); 26 | //validate appropriate error is thrown for missing password when signing up 27 | await expect(page.getByText('Signup requires a valid')).toBeVisible(); 28 | }); 29 | test('invalid username for signup', async ({ page }) => { 30 | await page.goto('http://localhost:3000/login'); 31 | 32 | await page.getByPlaceholder('you@example.com').fill('dummyEmail'); 33 | await page.getByPlaceholder('••••••••').fill('dummypassword'); 34 | await page.getByRole('button', { name: 'Sign Up' }).click(); 35 | //validate appropriate error is thrown for invalid username when signing up 36 | await expect(page.getByText('Unable to validate email')).toBeVisible(); 37 | }); 38 | test('password reset message', async ({ page }) => { 39 | await page.goto('http://localhost:3000/login'); 40 | await page.getByPlaceholder('you@example.com').fill('demo@gmail.com'); 41 | await page.getByRole('button', { name: 'Reset' }).click(); 42 | //validate appropriate message is shown 43 | await expect(page.getByText('Check email to reset password')).toBeVisible(); 44 | }); 45 | 46 | //more tests can be added here -------------------------------------------------------------------------------- /nyro-chatbot/app/[locale]/[workspaceid]/chat/[chatid]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { ChatUI } from "@/components/chat/chat-ui" 4 | 5 | export default function ChatIDPage() { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /nyro-chatbot/app/[locale]/[workspaceid]/chat/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { ChatHelp } from "@/components/chat/chat-help" 4 | import { useChatHandler } from "@/components/chat/chat-hooks/use-chat-handler" 5 | import { ChatInput } from "@/components/chat/chat-input" 6 | import { ChatSettings } from "@/components/chat/chat-settings" 7 | import { ChatUI } from "@/components/chat/chat-ui" 8 | import { QuickSettings } from "@/components/chat/quick-settings" 9 | import { Brand } from "@/components/ui/brand" 10 | import { NyroContext } from "@/context/context" 11 | import useHotkey from "@/lib/hooks/use-hotkey" 12 | import { useTheme } from "next-themes" 13 | import { useContext } from "react" 14 | 15 | export default function ChatPage() { 16 | useHotkey("o", () => handleNewChat()) 17 | useHotkey("l", () => { 18 | handleFocusChatInput() 19 | }) 20 | 21 | const { chatMessages, handleInputBlur, handleInputFocus } = useContext(NyroContext) 22 | 23 | const { handleNewChat, handleFocusChatInput } = useChatHandler() 24 | 25 | const { theme } = useTheme() 26 | 27 | return ( 28 | <> 29 | {chatMessages.length === 0 ? ( 30 |
31 | {/*
32 | 33 |
*/} 34 | 35 | {/*
36 | 37 |
*/} 38 | 39 |
40 | 41 |
42 | 43 |
44 | 45 |
46 | 47 |
48 | 49 |
50 | 51 |
52 |
53 | ) : ( 54 | 55 | )} 56 | 57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /nyro-chatbot/app/[locale]/[workspaceid]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { NyroContext } from "@/context/context" 4 | import { useContext } from "react" 5 | 6 | export default function WorkspacePage() { 7 | const { selectedWorkspace } = useContext(NyroContext) 8 | 9 | return ( 10 |
11 |
{selectedWorkspace?.name}
12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /nyro-chatbot/app/[locale]/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | ::-webkit-scrollbar-track { 6 | background-color: transparent; 7 | } 8 | 9 | ::-webkit-scrollbar-thumb { 10 | background-color: #ccc; 11 | border-radius: 10px; 12 | } 13 | 14 | ::-webkit-scrollbar-thumb:hover { 15 | background-color: #aaa; 16 | } 17 | 18 | ::-webkit-scrollbar-track:hover { 19 | background-color: #f2f2f2; 20 | } 21 | 22 | ::-webkit-scrollbar-corner { 23 | background-color: transparent; 24 | } 25 | 26 | ::-webkit-scrollbar { 27 | width: 6px; 28 | height: 6px; 29 | } 30 | 31 | @layer base { 32 | :root { 33 | --background: 0 0% 100%; 34 | --foreground: 0 0% 3.9%; 35 | 36 | --muted: 0 0% 96.1%; 37 | --muted-foreground: 0 0% 45.1%; 38 | 39 | --popover: 0 0% 100%; 40 | --popover-foreground: 0 0% 3.9%; 41 | 42 | --card: 0 0% 100%; 43 | --card-foreground: 0 0% 3.9%; 44 | 45 | --border: 0 0% 89.8%; 46 | --input: 0 0% 89.8%; 47 | 48 | --primary: 0 0% 9%; 49 | --primary-foreground: 0 0% 98%; 50 | 51 | --secondary: 0 0% 96.1%; 52 | --secondary-foreground: 0 0% 9%; 53 | 54 | --accent: 0 0% 96.1%; 55 | --accent-foreground: 0 0% 9%; 56 | 57 | --destructive: 0 84.2% 60.2%; 58 | --destructive-foreground: 0 0% 98%; 59 | 60 | --ring: 0 0% 63.9%; 61 | 62 | --radius: 0.5rem; 63 | } 64 | 65 | .dark { 66 | --background: 0 0% 3.9%; 67 | --foreground: 0 0% 98%; 68 | 69 | --muted: 0 0% 14.9%; 70 | --muted-foreground: 0 0% 63.9%; 71 | 72 | --popover: 0 0% 3.9%; 73 | --popover-foreground: 0 0% 98%; 74 | 75 | --card: 0 0% 3.9%; 76 | --card-foreground: 0 0% 98%; 77 | 78 | --border: 0 0% 14.9%; 79 | --input: 0 0% 14.9%; 80 | 81 | --primary: 0 0% 98%; 82 | --primary-foreground: 0 0% 9%; 83 | 84 | --secondary: 0 0% 14.9%; 85 | --secondary-foreground: 0 0% 98%; 86 | 87 | --accent: 0 0% 14.9%; 88 | --accent-foreground: 0 0% 98%; 89 | 90 | --destructive: 0 62.8% 30.6%; 91 | --destructive-foreground: 0 85.7% 97.3%; 92 | 93 | --ring: 0 0% 14.9%; 94 | } 95 | } 96 | 97 | @layer base { 98 | * { 99 | @apply border-border; 100 | } 101 | body { 102 | @apply text-foreground; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /nyro-chatbot/app/[locale]/help/page.tsx: -------------------------------------------------------------------------------- 1 | export default function HelpPage() { 2 | return ( 3 |
4 |
Contact us at hello@trynyro.com
5 |
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /nyro-chatbot/app/[locale]/loading.tsx: -------------------------------------------------------------------------------- 1 | import { IconLoader2 } from "@tabler/icons-react" 2 | 3 | export default function Loading() { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /nyro-chatbot/app/[locale]/login/password/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { ChangePassword } from "@/components/utility/change-password" 4 | import { supabase } from "@/lib/supabase/browser-client" 5 | import { useRouter } from "next/navigation" 6 | import { useEffect, useState } from "react" 7 | 8 | export default function ChangePasswordPage() { 9 | const [loading, setLoading] = useState(true) 10 | 11 | const router = useRouter() 12 | 13 | useEffect(() => { 14 | ;(async () => { 15 | const session = (await supabase.auth.getSession()).data.session 16 | 17 | if (!session) { 18 | router.push("/login") 19 | } else { 20 | setLoading(false) 21 | } 22 | })() 23 | }, []) 24 | 25 | if (loading) { 26 | return null 27 | } 28 | 29 | return 30 | } 31 | -------------------------------------------------------------------------------- /nyro-chatbot/app/[locale]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { NyroSVG } from "@/components/icons/nyro-svg" 4 | import { IconArrowRight } from "@tabler/icons-react" 5 | import { useTheme } from "next-themes" 6 | import Link from "next/link" 7 | 8 | export default function HomePage() { 9 | const { theme } = useTheme() 10 | 11 | return ( 12 |
13 |
14 | 15 |
16 | 17 | {/*
Nyro
*/} 18 | 19 | 23 | Start Chatting 24 | 25 | 26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /nyro-chatbot/app/api/assistants/openai/route.ts: -------------------------------------------------------------------------------- 1 | import { checkApiKey, getServerProfile } from "@/lib/server/server-chat-helpers" 2 | import { ServerRuntime } from "next" 3 | import OpenAI from "openai" 4 | 5 | export const runtime: ServerRuntime = "edge" 6 | 7 | export async function GET() { 8 | try { 9 | const profile = await getServerProfile() 10 | 11 | checkApiKey(profile.openai_api_key, "OpenAI") 12 | 13 | const openai = new OpenAI({ 14 | apiKey: profile.openai_api_key || "", 15 | organization: profile.openai_organization_id 16 | }) 17 | 18 | const myAssistants = await openai.beta.assistants.list({ 19 | limit: 100 20 | }) 21 | 22 | return new Response(JSON.stringify({ assistants: myAssistants.data }), { 23 | status: 200 24 | }) 25 | } catch (error: any) { 26 | const errorMessage = error.error?.message || "An unexpected error occurred" 27 | const errorCode = error.status || 500 28 | return new Response(JSON.stringify({ message: errorMessage }), { 29 | status: errorCode 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /nyro-chatbot/app/api/chat/custom/route.ts: -------------------------------------------------------------------------------- 1 | import { Database } from "@/supabase/types" 2 | import { ChatSettings } from "@/types" 3 | import { createClient } from "@supabase/supabase-js" 4 | import { OpenAIStream, StreamingTextResponse } from "ai" 5 | import { ServerRuntime } from "next" 6 | import OpenAI from "openai" 7 | import { ChatCompletionCreateParamsBase } from "openai/resources/chat/completions.mjs" 8 | 9 | export const runtime: ServerRuntime = "edge" 10 | 11 | export async function POST(request: Request) { 12 | const json = await request.json() 13 | const { chatSettings, messages, customModelId } = json as { 14 | chatSettings: ChatSettings 15 | messages: any[] 16 | customModelId: string 17 | } 18 | 19 | try { 20 | const supabaseAdmin = createClient( 21 | process.env.NEXT_PUBLIC_SUPABASE_URL!, 22 | process.env.SUPABASE_SERVICE_ROLE_KEY! 23 | ) 24 | 25 | const { data: customModel, error } = await supabaseAdmin 26 | .from("models") 27 | .select("*") 28 | .eq("id", customModelId) 29 | .single() 30 | 31 | if (!customModel) { 32 | throw new Error(error.message) 33 | } 34 | 35 | const custom = new OpenAI({ 36 | apiKey: customModel.api_key || "", 37 | baseURL: customModel.base_url 38 | }) 39 | 40 | const response = await custom.chat.completions.create({ 41 | model: chatSettings.model as ChatCompletionCreateParamsBase["model"], 42 | messages: messages as ChatCompletionCreateParamsBase["messages"], 43 | temperature: chatSettings.temperature, 44 | stream: true 45 | }) 46 | 47 | const stream = OpenAIStream(response) 48 | 49 | return new StreamingTextResponse(stream) 50 | } catch (error: any) { 51 | let errorMessage = error.message || "An unexpected error occurred" 52 | const errorCode = error.status || 500 53 | 54 | if (errorMessage.toLowerCase().includes("api key not found")) { 55 | errorMessage = 56 | "Custom API Key not found. Please set it in your profile settings." 57 | } else if (errorMessage.toLowerCase().includes("incorrect api key")) { 58 | errorMessage = 59 | "Custom API Key is incorrect. Please fix it in your profile settings." 60 | } 61 | 62 | return new Response(JSON.stringify({ message: errorMessage }), { 63 | status: errorCode 64 | }) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /nyro-chatbot/app/api/chat/google/route.ts: -------------------------------------------------------------------------------- 1 | import { checkApiKey, getServerProfile } from "@/lib/server/server-chat-helpers" 2 | import { ChatSettings } from "@/types" 3 | import { GoogleGenerativeAI } from "@google/generative-ai" 4 | 5 | export const runtime = "edge" 6 | 7 | export async function POST(request: Request) { 8 | const json = await request.json() 9 | const { chatSettings, messages } = json as { 10 | chatSettings: ChatSettings 11 | messages: any[] 12 | } 13 | 14 | try { 15 | const profile = await getServerProfile() 16 | 17 | checkApiKey(profile.google_gemini_api_key, "Google") 18 | 19 | const genAI = new GoogleGenerativeAI(profile.google_gemini_api_key || "") 20 | const googleModel = genAI.getGenerativeModel({ model: chatSettings.model }) 21 | 22 | const lastMessage = messages.pop() 23 | 24 | const chat = googleModel.startChat({ 25 | history: messages, 26 | generationConfig: { 27 | temperature: chatSettings.temperature 28 | } 29 | }) 30 | 31 | const response = await chat.sendMessageStream(lastMessage.parts) 32 | 33 | const encoder = new TextEncoder() 34 | const readableStream = new ReadableStream({ 35 | async start(controller) { 36 | for await (const chunk of response.stream) { 37 | const chunkText = chunk.text() 38 | controller.enqueue(encoder.encode(chunkText)) 39 | } 40 | controller.close() 41 | } 42 | }) 43 | 44 | return new Response(readableStream, { 45 | headers: { "Content-Type": "text/plain" } 46 | }) 47 | 48 | } catch (error: any) { 49 | let errorMessage = error.message || "An unexpected error occurred" 50 | const errorCode = error.status || 500 51 | 52 | if (errorMessage.toLowerCase().includes("api key not found")) { 53 | errorMessage = 54 | "Google Gemini API Key not found. Please set it in your profile settings." 55 | } else if (errorMessage.toLowerCase().includes("api key not valid")) { 56 | errorMessage = 57 | "Google Gemini API Key is incorrect. Please fix it in your profile settings." 58 | } 59 | 60 | return new Response(JSON.stringify({ message: errorMessage }), { 61 | status: errorCode 62 | }) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /nyro-chatbot/app/api/chat/groq/route.ts: -------------------------------------------------------------------------------- 1 | import { CHAT_SETTING_LIMITS } from "@/lib/chat-setting-limits" 2 | import { checkApiKey, getServerProfile } from "@/lib/server/server-chat-helpers" 3 | import { ChatSettings } from "@/types" 4 | import { OpenAIStream, StreamingTextResponse } from "ai" 5 | import OpenAI from "openai" 6 | 7 | export const runtime = "edge" 8 | export async function POST(request: Request) { 9 | const json = await request.json() 10 | const { chatSettings, messages } = json as { 11 | chatSettings: ChatSettings 12 | messages: any[] 13 | } 14 | 15 | try { 16 | const profile = await getServerProfile() 17 | 18 | checkApiKey(profile.groq_api_key, "G") 19 | 20 | // Groq is compatible with the OpenAI SDK 21 | const groq = new OpenAI({ 22 | apiKey: profile.groq_api_key || "", 23 | baseURL: "https://api.groq.com/openai/v1" 24 | }) 25 | 26 | const response = await groq.chat.completions.create({ 27 | model: chatSettings.model, 28 | messages, 29 | max_tokens: 30 | CHAT_SETTING_LIMITS[chatSettings.model].MAX_TOKEN_OUTPUT_LENGTH, 31 | stream: true 32 | }) 33 | 34 | // Convert the response into a friendly text-stream. 35 | const stream = OpenAIStream(response) 36 | 37 | // Respond with the stream 38 | return new StreamingTextResponse(stream) 39 | } catch (error: any) { 40 | let errorMessage = error.message || "An unexpected error occurred" 41 | const errorCode = error.status || 500 42 | 43 | if (errorMessage.toLowerCase().includes("api key not found")) { 44 | errorMessage = 45 | "Groq API Key not found. Please set it in your profile settings." 46 | } else if (errorCode === 401) { 47 | errorMessage = 48 | "Groq API Key is incorrect. Please fix it in your profile settings." 49 | } 50 | 51 | return new Response(JSON.stringify({ message: errorMessage }), { 52 | status: errorCode 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /nyro-chatbot/app/api/chat/mistral/route.ts: -------------------------------------------------------------------------------- 1 | import { CHAT_SETTING_LIMITS } from "@/lib/chat-setting-limits" 2 | import { checkApiKey, getServerProfile } from "@/lib/server/server-chat-helpers" 3 | import { ChatSettings } from "@/types" 4 | import { OpenAIStream, StreamingTextResponse } from "ai" 5 | import OpenAI from "openai" 6 | 7 | export const runtime = "edge" 8 | 9 | export async function POST(request: Request) { 10 | const json = await request.json() 11 | const { chatSettings, messages } = json as { 12 | chatSettings: ChatSettings 13 | messages: any[] 14 | } 15 | 16 | try { 17 | const profile = await getServerProfile() 18 | 19 | checkApiKey(profile.mistral_api_key, "Mistral") 20 | 21 | // Mistral is compatible the OpenAI SDK 22 | const mistral = new OpenAI({ 23 | apiKey: profile.mistral_api_key || "", 24 | baseURL: "https://api.mistral.ai/v1" 25 | }) 26 | 27 | const response = await mistral.chat.completions.create({ 28 | model: chatSettings.model, 29 | messages, 30 | max_tokens: 31 | CHAT_SETTING_LIMITS[chatSettings.model].MAX_TOKEN_OUTPUT_LENGTH, 32 | stream: true 33 | }) 34 | 35 | // Convert the response into a friendly text-stream. 36 | const stream = OpenAIStream(response) 37 | 38 | // Respond with the stream 39 | return new StreamingTextResponse(stream) 40 | } catch (error: any) { 41 | let errorMessage = error.message || "An unexpected error occurred" 42 | const errorCode = error.status || 500 43 | 44 | if (errorMessage.toLowerCase().includes("api key not found")) { 45 | errorMessage = 46 | "Mistral API Key not found. Please set it in your profile settings." 47 | } else if (errorCode === 401) { 48 | errorMessage = 49 | "Mistral API Key is incorrect. Please fix it in your profile settings." 50 | } 51 | 52 | return new Response(JSON.stringify({ message: errorMessage }), { 53 | status: errorCode 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /nyro-chatbot/app/api/chat/openai/route.ts: -------------------------------------------------------------------------------- 1 | import { checkApiKey, getServerProfile } from "@/lib/server/server-chat-helpers" 2 | import { ChatSettings } from "@/types" 3 | import { OpenAIStream, StreamingTextResponse } from "ai" 4 | import { ServerRuntime } from "next" 5 | import OpenAI from "openai" 6 | import { ChatCompletionCreateParamsBase } from "openai/resources/chat/completions.mjs" 7 | 8 | export const runtime: ServerRuntime = "edge" 9 | 10 | export async function POST(request: Request) { 11 | const json = await request.json() 12 | const { chatSettings, messages } = json as { 13 | chatSettings: ChatSettings 14 | messages: any[] 15 | } 16 | 17 | try { 18 | const profile = await getServerProfile() 19 | 20 | checkApiKey(profile.openai_api_key, "OpenAI") 21 | 22 | const openai = new OpenAI({ 23 | apiKey: profile.openai_api_key || "", 24 | organization: profile.openai_organization_id 25 | }) 26 | 27 | const response = await openai.chat.completions.create({ 28 | model: chatSettings.model as ChatCompletionCreateParamsBase["model"], 29 | messages: messages as ChatCompletionCreateParamsBase["messages"], 30 | temperature: chatSettings.temperature, 31 | max_tokens: 32 | chatSettings.model === "gpt-4-vision-preview" || 33 | chatSettings.model === "gpt-4o" 34 | ? 4096 35 | : 4096, // TODO: Fix 36 | stream: true 37 | }) 38 | 39 | const stream = OpenAIStream(response) 40 | 41 | return new StreamingTextResponse(stream) 42 | } catch (error: any) { 43 | let errorMessage = error.message || "An unexpected error occurred" 44 | const errorCode = error.status || 500 45 | 46 | if (errorMessage.toLowerCase().includes("api key not found")) { 47 | errorMessage = 48 | "OpenAI API Key not found. Please set it in your profile settings." 49 | } else if (errorMessage.toLowerCase().includes("incorrect api key")) { 50 | errorMessage = 51 | "OpenAI API Key is incorrect. Please fix it in your profile settings." 52 | } 53 | 54 | return new Response(JSON.stringify({ message: errorMessage }), { 55 | status: errorCode 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /nyro-chatbot/app/api/chat/openrouter/route.ts: -------------------------------------------------------------------------------- 1 | import { checkApiKey, getServerProfile } from "@/lib/server/server-chat-helpers" 2 | import { ChatSettings } from "@/types" 3 | import { OpenAIStream, StreamingTextResponse } from "ai" 4 | import { ServerRuntime } from "next" 5 | import OpenAI from "openai" 6 | import { ChatCompletionCreateParamsBase } from "openai/resources/chat/completions.mjs" 7 | 8 | export const runtime: ServerRuntime = "edge" 9 | 10 | export async function POST(request: Request) { 11 | const json = await request.json() 12 | const { chatSettings, messages } = json as { 13 | chatSettings: ChatSettings 14 | messages: any[] 15 | } 16 | 17 | try { 18 | const profile = await getServerProfile() 19 | 20 | checkApiKey(profile.openrouter_api_key, "OpenRouter") 21 | 22 | const openai = new OpenAI({ 23 | apiKey: profile.openrouter_api_key || "", 24 | baseURL: "https://openrouter.ai/api/v1" 25 | }) 26 | 27 | const response = await openai.chat.completions.create({ 28 | model: chatSettings.model as ChatCompletionCreateParamsBase["model"], 29 | messages: messages as ChatCompletionCreateParamsBase["messages"], 30 | temperature: chatSettings.temperature, 31 | max_tokens: undefined, 32 | stream: true 33 | }) 34 | 35 | const stream = OpenAIStream(response) 36 | 37 | return new StreamingTextResponse(stream) 38 | } catch (error: any) { 39 | let errorMessage = error.message || "An unexpected error occurred" 40 | const errorCode = error.status || 500 41 | 42 | if (errorMessage.toLowerCase().includes("api key not found")) { 43 | errorMessage = 44 | "OpenRouter API Key not found. Please set it in your profile settings." 45 | } 46 | 47 | return new Response(JSON.stringify({ message: errorMessage }), { 48 | status: errorCode 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /nyro-chatbot/app/api/chat/perplexity/route.ts: -------------------------------------------------------------------------------- 1 | import { checkApiKey, getServerProfile } from "@/lib/server/server-chat-helpers" 2 | import { ChatSettings } from "@/types" 3 | import { OpenAIStream, StreamingTextResponse } from "ai" 4 | import OpenAI from "openai" 5 | 6 | export const runtime = "edge" 7 | 8 | export async function POST(request: Request) { 9 | const json = await request.json() 10 | const { chatSettings, messages } = json as { 11 | chatSettings: ChatSettings 12 | messages: any[] 13 | } 14 | 15 | try { 16 | const profile = await getServerProfile() 17 | 18 | checkApiKey(profile.perplexity_api_key, "Perplexity") 19 | 20 | // Perplexity is compatible the OpenAI SDK 21 | const perplexity = new OpenAI({ 22 | apiKey: profile.perplexity_api_key || "", 23 | baseURL: "https://api.perplexity.ai/" 24 | }) 25 | 26 | const response = await perplexity.chat.completions.create({ 27 | model: chatSettings.model, 28 | messages, 29 | stream: true 30 | }) 31 | 32 | const stream = OpenAIStream(response) 33 | 34 | return new StreamingTextResponse(stream) 35 | } catch (error: any) { 36 | let errorMessage = error.message || "An unexpected error occurred" 37 | const errorCode = error.status || 500 38 | 39 | if (errorMessage.toLowerCase().includes("api key not found")) { 40 | errorMessage = 41 | "Perplexity API Key not found. Please set it in your profile settings." 42 | } else if (errorCode === 401) { 43 | errorMessage = 44 | "Perplexity API Key is incorrect. Please fix it in your profile settings." 45 | } 46 | 47 | return new Response(JSON.stringify({ message: errorMessage }), { 48 | status: errorCode 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /nyro-chatbot/app/api/command/route.ts: -------------------------------------------------------------------------------- 1 | import { CHAT_SETTING_LIMITS } from "@/lib/chat-setting-limits" 2 | import { checkApiKey, getServerProfile } from "@/lib/server/server-chat-helpers" 3 | import OpenAI from "openai" 4 | 5 | export const runtime = "edge" 6 | 7 | export async function POST(request: Request) { 8 | const json = await request.json() 9 | const { input } = json as { 10 | input: string 11 | } 12 | 13 | try { 14 | const profile = await getServerProfile() 15 | 16 | checkApiKey(profile.openai_api_key, "OpenAI") 17 | 18 | const openai = new OpenAI({ 19 | apiKey: profile.openai_api_key || "", 20 | organization: profile.openai_organization_id 21 | }) 22 | 23 | const response = await openai.chat.completions.create({ 24 | model: "gpt-4-1106-preview", 25 | messages: [ 26 | { 27 | role: "system", 28 | content: "Respond to the user." 29 | }, 30 | { 31 | role: "user", 32 | content: input 33 | } 34 | ], 35 | temperature: 0, 36 | max_tokens: 37 | CHAT_SETTING_LIMITS["gpt-4-turbo-preview"].MAX_TOKEN_OUTPUT_LENGTH 38 | // response_format: { type: "json_object" } 39 | // stream: true 40 | }) 41 | 42 | const content = response.choices[0].message.content 43 | 44 | return new Response(JSON.stringify({ content }), { 45 | status: 200 46 | }) 47 | } catch (error: any) { 48 | const errorMessage = error.error?.message || "An unexpected error occurred" 49 | const errorCode = error.status || 500 50 | return new Response(JSON.stringify({ message: errorMessage }), { 51 | status: errorCode 52 | }) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /nyro-chatbot/app/api/keys/route.ts: -------------------------------------------------------------------------------- 1 | import { isUsingEnvironmentKey } from "@/lib/envs" 2 | import { createResponse } from "@/lib/server/server-utils" 3 | import { EnvKey } from "@/types/key-type" 4 | import { VALID_ENV_KEYS } from "@/types/valid-keys" 5 | 6 | export async function GET() { 7 | const envKeyMap: Record = { 8 | azure: VALID_ENV_KEYS.AZURE_OPENAI_API_KEY, 9 | openai: VALID_ENV_KEYS.OPENAI_API_KEY, 10 | google: VALID_ENV_KEYS.GOOGLE_GEMINI_API_KEY, 11 | anthropic: VALID_ENV_KEYS.ANTHROPIC_API_KEY, 12 | mistral: VALID_ENV_KEYS.MISTRAL_API_KEY, 13 | groq: VALID_ENV_KEYS.GROQ_API_KEY, 14 | perplexity: VALID_ENV_KEYS.PERPLEXITY_API_KEY, 15 | openrouter: VALID_ENV_KEYS.OPENROUTER_API_KEY, 16 | 17 | openai_organization_id: VALID_ENV_KEYS.OPENAI_ORGANIZATION_ID, 18 | 19 | azure_openai_endpoint: VALID_ENV_KEYS.AZURE_OPENAI_ENDPOINT, 20 | azure_gpt_35_turbo_name: VALID_ENV_KEYS.AZURE_GPT_35_TURBO_NAME, 21 | azure_gpt_45_vision_name: VALID_ENV_KEYS.AZURE_GPT_45_VISION_NAME, 22 | azure_gpt_45_turbo_name: VALID_ENV_KEYS.AZURE_GPT_45_TURBO_NAME, 23 | azure_embeddings_name: VALID_ENV_KEYS.AZURE_EMBEDDINGS_NAME 24 | } 25 | 26 | const isUsingEnvKeyMap = Object.keys(envKeyMap).reduce< 27 | Record 28 | >((acc, provider) => { 29 | const key = envKeyMap[provider] 30 | 31 | if (key) { 32 | acc[provider] = isUsingEnvironmentKey(key as EnvKey) 33 | } 34 | return acc 35 | }, {}) 36 | 37 | return createResponse({ isUsingEnvKeyMap }, 200) 38 | } 39 | -------------------------------------------------------------------------------- /nyro-chatbot/app/api/username/available/route.ts: -------------------------------------------------------------------------------- 1 | import { Database } from "@/supabase/types" 2 | import { createClient } from "@supabase/supabase-js" 3 | 4 | export const runtime = "edge" 5 | 6 | export async function POST(request: Request) { 7 | const json = await request.json() 8 | const { username } = json as { 9 | username: string 10 | } 11 | 12 | try { 13 | const supabaseAdmin = createClient( 14 | process.env.NEXT_PUBLIC_SUPABASE_URL!, 15 | process.env.SUPABASE_SERVICE_ROLE_KEY! 16 | ) 17 | 18 | const { data: usernames, error } = await supabaseAdmin 19 | .from("profiles") 20 | .select("username") 21 | .eq("username", username) 22 | 23 | if (!usernames) { 24 | throw new Error(error.message) 25 | } 26 | 27 | return new Response(JSON.stringify({ isAvailable: !usernames.length }), { 28 | status: 200 29 | }) 30 | } catch (error: any) { 31 | const errorMessage = error.error?.message || "An unexpected error occurred" 32 | const errorCode = error.status || 500 33 | return new Response(JSON.stringify({ message: errorMessage }), { 34 | status: errorCode 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /nyro-chatbot/app/api/username/get/route.ts: -------------------------------------------------------------------------------- 1 | import { Database } from "@/supabase/types" 2 | import { createClient } from "@supabase/supabase-js" 3 | 4 | export const runtime = "edge" 5 | 6 | export async function POST(request: Request) { 7 | const json = await request.json() 8 | const { userId } = json as { 9 | userId: string 10 | } 11 | 12 | try { 13 | const supabaseAdmin = createClient( 14 | process.env.NEXT_PUBLIC_SUPABASE_URL!, 15 | process.env.SUPABASE_SERVICE_ROLE_KEY! 16 | ) 17 | 18 | const { data, error } = await supabaseAdmin 19 | .from("profiles") 20 | .select("username") 21 | .eq("user_id", userId) 22 | .single() 23 | 24 | if (!data) { 25 | throw new Error(error.message) 26 | } 27 | 28 | return new Response(JSON.stringify({ username: data.username }), { 29 | status: 200 30 | }) 31 | } catch (error: any) { 32 | const errorMessage = error.error?.message || "An unexpected error occurred" 33 | const errorCode = error.status || 500 34 | return new Response(JSON.stringify({ message: errorMessage }), { 35 | status: errorCode 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /nyro-chatbot/app/auth/callback/route.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from "@/lib/supabase/server" 2 | import { cookies } from "next/headers" 3 | import { NextResponse } from "next/server" 4 | 5 | export async function GET(request: Request) { 6 | const requestUrl = new URL(request.url) 7 | const code = requestUrl.searchParams.get("code") 8 | const next = requestUrl.searchParams.get("next") 9 | 10 | if (code) { 11 | const cookieStore = cookies() 12 | const supabase = createClient(cookieStore) 13 | await supabase.auth.exchangeCodeForSession(code) 14 | } 15 | 16 | if (next) { 17 | return NextResponse.redirect(requestUrl.origin + next) 18 | } else { 19 | return NextResponse.redirect(requestUrl.origin) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /nyro-chatbot/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "app/globals.css", 9 | "baseColor": "gray", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /nyro-chatbot/components/chat/chat-command-input.tsx: -------------------------------------------------------------------------------- 1 | import { NyroContext } from "@/context/context" 2 | import { FC, useContext } from "react" 3 | import { AssistantPicker } from "./assistant-picker" 4 | import { usePromptAndCommand } from "./chat-hooks/use-prompt-and-command" 5 | import { FilePicker } from "./file-picker" 6 | import { PromptPicker } from "./prompt-picker" 7 | import { ToolPicker } from "./tool-picker" 8 | 9 | interface ChatCommandInputProps {} 10 | 11 | export const ChatCommandInput: FC = ({}) => { 12 | const { 13 | newMessageFiles, 14 | chatFiles, 15 | slashCommand, 16 | isFilePickerOpen, 17 | setIsFilePickerOpen, 18 | hashtagCommand, 19 | focusPrompt, 20 | focusFile 21 | } = useContext(NyroContext) 22 | 23 | const { handleSelectUserFile, handleSelectUserCollection } = 24 | usePromptAndCommand() 25 | 26 | return ( 27 | <> 28 | 29 | 30 | file.id 36 | )} 37 | selectedCollectionIds={[]} 38 | onSelectFile={handleSelectUserFile} 39 | onSelectCollection={handleSelectUserCollection} 40 | isFocused={focusFile} 41 | /> 42 | 43 | 44 | 45 | 46 | 47 | ) 48 | } 49 | -------------------------------------------------------------------------------- /nyro-chatbot/components/chat/chat-hooks/use-scroll.tsx: -------------------------------------------------------------------------------- 1 | import { NyroContext } from "@/context/context" 2 | import { 3 | type UIEventHandler, 4 | useCallback, 5 | useContext, 6 | useEffect, 7 | useRef, 8 | useState 9 | } from "react" 10 | 11 | export const useScroll = () => { 12 | const { isGenerating, chatMessages } = useContext(NyroContext) 13 | 14 | const messagesStartRef = useRef(null) 15 | const messagesEndRef = useRef(null) 16 | const isAutoScrolling = useRef(false) 17 | 18 | const [isAtTop, setIsAtTop] = useState(false) 19 | const [isAtBottom, setIsAtBottom] = useState(true) 20 | const [userScrolled, setUserScrolled] = useState(false) 21 | const [isOverflowing, setIsOverflowing] = useState(false) 22 | 23 | useEffect(() => { 24 | setUserScrolled(false) 25 | 26 | if (!isGenerating && userScrolled) { 27 | setUserScrolled(false) 28 | } 29 | }, [isGenerating]) 30 | 31 | useEffect(() => { 32 | if (isGenerating && !userScrolled) { 33 | scrollToBottom() 34 | } 35 | }, [chatMessages]) 36 | 37 | const handleScroll: UIEventHandler = useCallback(e => { 38 | const target = e.target as HTMLDivElement 39 | const bottom = 40 | Math.round(target.scrollHeight) - Math.round(target.scrollTop) === 41 | Math.round(target.clientHeight) 42 | setIsAtBottom(bottom) 43 | 44 | const top = target.scrollTop === 0 45 | setIsAtTop(top) 46 | 47 | if (!bottom && !isAutoScrolling.current) { 48 | setUserScrolled(true) 49 | } else { 50 | setUserScrolled(false) 51 | } 52 | 53 | const isOverflow = target.scrollHeight > target.clientHeight 54 | setIsOverflowing(isOverflow) 55 | }, []) 56 | 57 | const scrollToTop = useCallback(() => { 58 | if (messagesStartRef.current) { 59 | messagesStartRef.current.scrollIntoView({ behavior: "instant" }) 60 | } 61 | }, []) 62 | 63 | const scrollToBottom = useCallback(() => { 64 | isAutoScrolling.current = true 65 | 66 | setTimeout(() => { 67 | if (messagesEndRef.current) { 68 | messagesEndRef.current.scrollIntoView({ behavior: "instant" }) 69 | } 70 | 71 | isAutoScrolling.current = false 72 | }, 100) 73 | }, []) 74 | 75 | return { 76 | messagesStartRef, 77 | messagesEndRef, 78 | isAtTop, 79 | isAtBottom, 80 | userScrolled, 81 | isOverflowing, 82 | handleScroll, 83 | scrollToTop, 84 | scrollToBottom, 85 | setIsAtBottom 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /nyro-chatbot/components/chat/chat-messages.tsx: -------------------------------------------------------------------------------- 1 | import { useChatHandler } from "@/components/chat/chat-hooks/use-chat-handler" 2 | import { NyroContext } from "@/context/context" 3 | import { Tables } from "@/supabase/types" 4 | import { FC, useContext, useState } from "react" 5 | import { Message } from "../messages/message" 6 | 7 | interface ChatMessagesProps {} 8 | 9 | export const ChatMessages: FC = ({}) => { 10 | const { chatMessages, chatFileItems } = useContext(NyroContext) 11 | 12 | const { handleSendEdit } = useChatHandler() 13 | 14 | const [editingMessage, setEditingMessage] = useState>() 15 | 16 | return chatMessages 17 | .sort((a, b) => a.message.sequence_number - b.message.sequence_number) 18 | .map((chatMessage, index, array) => { 19 | const messageFileItems = chatFileItems.filter( 20 | (chatFileItem, _, self) => 21 | chatMessage.fileItems.includes(chatFileItem.id) && 22 | self.findIndex(item => item.id === chatFileItem.id) === _ 23 | ) 24 | 25 | return ( 26 | setEditingMessage(undefined)} 34 | onSubmitEdit={handleSendEdit} 35 | /> 36 | ) 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /nyro-chatbot/components/chat/chat-retrieval-settings.tsx: -------------------------------------------------------------------------------- 1 | import { NyroContext } from "@/context/context" 2 | import { IconAdjustmentsHorizontal } from "@tabler/icons-react" 3 | import { FC, useContext, useState } from "react" 4 | import { Button } from "../ui/button" 5 | import { 6 | Dialog, 7 | DialogContent, 8 | DialogFooter, 9 | DialogTrigger 10 | } from "../ui/dialog" 11 | import { Label } from "../ui/label" 12 | import { Slider } from "../ui/slider" 13 | import { WithTooltip } from "../ui/with-tooltip" 14 | 15 | interface ChatRetrievalSettingsProps {} 16 | 17 | export const ChatRetrievalSettings: FC = ({}) => { 18 | const { sourceCount, setSourceCount } = useContext(NyroContext) 19 | 20 | const [isOpen, setIsOpen] = useState(false) 21 | 22 | return ( 23 | 24 | 25 | Adjust retrieval settings.
} 29 | trigger={ 30 | 34 | } 35 | /> 36 | 37 | 38 | 39 |
40 | 45 | 46 | { 49 | setSourceCount(values[0]) 50 | }} 51 | min={1} 52 | max={10} 53 | step={1} 54 | /> 55 |
56 | 57 | 58 | 61 | 62 |
63 | 64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /nyro-chatbot/components/chat/chat-scroll-buttons.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IconCircleArrowDownFilled, 3 | IconCircleArrowUpFilled 4 | } from "@tabler/icons-react" 5 | import { FC } from "react" 6 | 7 | interface ChatScrollButtonsProps { 8 | isAtTop: boolean 9 | isAtBottom: boolean 10 | isOverflowing: boolean 11 | scrollToTop: () => void 12 | scrollToBottom: () => void 13 | } 14 | 15 | export const ChatScrollButtons: FC = ({ 16 | isAtTop, 17 | isAtBottom, 18 | isOverflowing, 19 | scrollToTop, 20 | scrollToBottom 21 | }) => { 22 | return ( 23 | <> 24 | {!isAtTop && isOverflowing && ( 25 | 30 | )} 31 | 32 | {!isAtBottom && isOverflowing && ( 33 | 38 | )} 39 | 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /nyro-chatbot/components/chat/quick-setting-option.tsx: -------------------------------------------------------------------------------- 1 | import { LLM_LIST } from "@/lib/models/llm/llm-list" 2 | import { Tables } from "@/supabase/types" 3 | import { IconCircleCheckFilled, IconRobotFace } from "@tabler/icons-react" 4 | import Image from "next/image" 5 | import { FC } from "react" 6 | import { ModelIcon } from "../models/model-icon" 7 | import { DropdownMenuItem } from "../ui/dropdown-menu" 8 | 9 | interface QuickSettingOptionProps { 10 | contentType: "presets" | "assistants" 11 | isSelected: boolean 12 | item: Tables<"presets"> | Tables<"assistants"> 13 | onSelect: () => void 14 | image: string 15 | } 16 | 17 | export const QuickSettingOption: FC = ({ 18 | contentType, 19 | isSelected, 20 | item, 21 | onSelect, 22 | image 23 | }) => { 24 | const modelDetails = LLM_LIST.find(model => model.modelId === item.model) 25 | 26 | return ( 27 | 32 |
33 | {contentType === "presets" ? ( 34 | 39 | ) : image ? ( 40 | Assistant 48 | ) : ( 49 | 53 | )} 54 |
55 | 56 |
57 |
{item.name}
58 | 59 | {item.description && ( 60 |
{item.description}
61 | )} 62 |
63 | 64 |
65 | {isSelected ? ( 66 | 67 | ) : null} 68 |
69 |
70 | ) 71 | } 72 | -------------------------------------------------------------------------------- /nyro-chatbot/components/icons/google-svg.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react" 2 | 3 | interface GoogleSVGProps { 4 | height?: number 5 | width?: number 6 | className?: string 7 | } 8 | 9 | export const GoogleSVG: FC = ({ 10 | height = 40, 11 | width = 40, 12 | className 13 | }) => { 14 | return ( 15 | 24 | 28 | 32 | 36 | 40 | 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /nyro-chatbot/components/messages/message-markdown-memoized.tsx: -------------------------------------------------------------------------------- 1 | import { FC, memo } from "react" 2 | import ReactMarkdown, { Options } from "react-markdown" 3 | 4 | export const MessageMarkdownMemoized: FC = memo( 5 | ReactMarkdown, 6 | (prevProps, nextProps) => 7 | prevProps.children === nextProps.children && 8 | prevProps.className === nextProps.className 9 | ) 10 | -------------------------------------------------------------------------------- /nyro-chatbot/components/messages/message-markdown.tsx: -------------------------------------------------------------------------------- 1 | import React, { FC } from "react" 2 | import remarkGfm from "remark-gfm" 3 | import remarkMath from "remark-math" 4 | import { MessageCodeBlock } from "./message-codeblock" 5 | import { MessageMarkdownMemoized } from "./message-markdown-memoized" 6 | 7 | interface MessageMarkdownProps { 8 | content: string 9 | } 10 | 11 | export const MessageMarkdown: FC = ({ content }) => { 12 | return ( 13 | {children}

19 | }, 20 | img({ node, ...props }) { 21 | return 22 | }, 23 | code({ node, className, children, ...props }) { 24 | const childArray = React.Children.toArray(children) 25 | const firstChild = childArray[0] as React.ReactElement 26 | const firstChildAsString = React.isValidElement(firstChild) 27 | ? (firstChild as React.ReactElement).props.children 28 | : firstChild 29 | 30 | if (firstChildAsString === "▍") { 31 | return 32 | } 33 | 34 | if (typeof firstChildAsString === "string") { 35 | childArray[0] = firstChildAsString.replace("`▍`", "▍") 36 | } 37 | 38 | const match = /language-(\w+)/.exec(className || "") 39 | 40 | if ( 41 | typeof firstChildAsString === "string" && 42 | !firstChildAsString.includes("\n") 43 | ) { 44 | return ( 45 | 46 | {childArray} 47 | 48 | ) 49 | } 50 | 51 | return ( 52 | 58 | ) 59 | } 60 | }} 61 | > 62 | {content} 63 |
64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /nyro-chatbot/components/messages/message-replies.tsx: -------------------------------------------------------------------------------- 1 | import { IconMessage } from "@tabler/icons-react" 2 | import { FC, useState } from "react" 3 | import { 4 | Sheet, 5 | SheetContent, 6 | SheetDescription, 7 | SheetHeader, 8 | SheetTitle, 9 | SheetTrigger 10 | } from "../ui/sheet" 11 | import { WithTooltip } from "../ui/with-tooltip" 12 | import { MESSAGE_ICON_SIZE } from "./message-actions" 13 | 14 | interface MessageRepliesProps {} 15 | 16 | export const MessageReplies: FC = ({}) => { 17 | const [isOpen, setIsOpen] = useState(false) 18 | 19 | return ( 20 | 21 | 22 | View Replies} 26 | trigger={ 27 |
setIsOpen(true)} 30 | > 31 | 32 |
33 | {1} 34 |
35 |
36 | } 37 | /> 38 |
39 | 40 | 41 | 42 | Are you sure absolutely sure? 43 | 44 | This action cannot be undone. This will permanently delete your 45 | account and remove your data from our servers. 46 | 47 | 48 | 49 |
50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /nyro-chatbot/components/models/model-option.tsx: -------------------------------------------------------------------------------- 1 | import { LLM } from "@/types" 2 | import { FC } from "react" 3 | import { ModelIcon } from "./model-icon" 4 | import { IconInfoCircle } from "@tabler/icons-react" 5 | import { WithTooltip } from "../ui/with-tooltip" 6 | 7 | interface ModelOptionProps { 8 | model: LLM 9 | onSelect: () => void 10 | } 11 | 12 | export const ModelOption: FC = ({ model, onSelect }) => { 13 | return ( 14 | 17 | {model.provider !== "ollama" && model.pricing && ( 18 |
19 |
20 | Input Cost:{" "} 21 | {model.pricing.inputCost} {model.pricing.currency} per{" "} 22 | {model.pricing.unit} 23 |
24 | {model.pricing.outputCost && ( 25 |
26 | Output Cost:{" "} 27 | {model.pricing.outputCost} {model.pricing.currency} per{" "} 28 | {model.pricing.unit} 29 |
30 | )} 31 |
32 | )} 33 | 34 | } 35 | side="bottom" 36 | trigger={ 37 |
41 |
42 | 43 |
{model.modelName}
44 |
45 |
46 | } 47 | /> 48 | ) 49 | } 50 | -------------------------------------------------------------------------------- /nyro-chatbot/components/setup/finish-step.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react" 2 | 3 | interface FinishStepProps { 4 | displayName: string 5 | } 6 | 7 | export const FinishStep: FC = ({ displayName }) => { 8 | return ( 9 |
10 |
11 | Welcome to Nyro 12 | {displayName.length > 0 ? `, ${displayName.split(" ")[0]}` : null}! 13 |
14 | 15 |
Click next to start chatting.
16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /nyro-chatbot/components/setup/step-container.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button" 2 | import { 3 | Card, 4 | CardContent, 5 | CardDescription, 6 | CardFooter, 7 | CardHeader, 8 | CardTitle 9 | } from "@/components/ui/card" 10 | import { FC, useRef } from "react" 11 | 12 | export const SETUP_STEP_COUNT = 3 13 | 14 | interface StepContainerProps { 15 | stepDescription: string 16 | stepNum: number 17 | stepTitle: string 18 | onShouldProceed: (shouldProceed: boolean) => void 19 | children?: React.ReactNode 20 | showBackButton?: boolean 21 | showNextButton?: boolean 22 | } 23 | 24 | export const StepContainer: FC = ({ 25 | stepDescription, 26 | stepNum, 27 | stepTitle, 28 | onShouldProceed, 29 | children, 30 | showBackButton = false, 31 | showNextButton = true 32 | }) => { 33 | const buttonRef = useRef(null) 34 | 35 | const handleKeyDown = (e: React.KeyboardEvent) => { 36 | if (e.key === "Enter" && !e.shiftKey) { 37 | if (buttonRef.current) { 38 | buttonRef.current.click() 39 | } 40 | } 41 | } 42 | 43 | return ( 44 | 48 | 49 | 50 |
{stepTitle}
51 | 52 |
53 | {stepNum} / {SETUP_STEP_COUNT} 54 |
55 |
56 | 57 | {stepDescription} 58 |
59 | 60 | {children} 61 | 62 | 63 |
64 | {showBackButton && ( 65 | 72 | )} 73 |
74 | 75 |
76 | {showNextButton && ( 77 | 84 | )} 85 |
86 |
87 |
88 | ) 89 | } 90 | -------------------------------------------------------------------------------- /nyro-chatbot/components/sidebar/items/chat/delete-chat.tsx: -------------------------------------------------------------------------------- 1 | import { useChatHandler } from "@/components/chat/chat-hooks/use-chat-handler" 2 | import { Button } from "@/components/ui/button" 3 | import { 4 | Dialog, 5 | DialogContent, 6 | DialogDescription, 7 | DialogFooter, 8 | DialogHeader, 9 | DialogTitle, 10 | DialogTrigger 11 | } from "@/components/ui/dialog" 12 | import { NyroContext } from "@/context/context" 13 | import { deleteChat } from "@/db/chats" 14 | import useHotkey from "@/lib/hooks/use-hotkey" 15 | import { Tables } from "@/supabase/types" 16 | import { IconTrash } from "@tabler/icons-react" 17 | import { FC, useContext, useRef, useState } from "react" 18 | 19 | interface DeleteChatProps { 20 | chat: Tables<"chats"> 21 | } 22 | 23 | export const DeleteChat: FC = ({ chat }) => { 24 | useHotkey("Backspace", () => setShowChatDialog(true)) 25 | 26 | const { setChats } = useContext(NyroContext) 27 | const { handleNewChat } = useChatHandler() 28 | 29 | const buttonRef = useRef(null) 30 | 31 | const [showChatDialog, setShowChatDialog] = useState(false) 32 | 33 | const handleDeleteChat = async () => { 34 | await deleteChat(chat.id) 35 | 36 | setChats(prevState => prevState.filter(c => c.id !== chat.id)) 37 | 38 | setShowChatDialog(false) 39 | 40 | handleNewChat() 41 | } 42 | 43 | const handleKeyDown = (e: React.KeyboardEvent) => { 44 | if (e.key === "Enter") { 45 | buttonRef.current?.click() 46 | } 47 | } 48 | 49 | return ( 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | Delete {chat.name} 58 | 59 | 60 | Are you sure you want to delete this chat? 61 | 62 | 63 | 64 | 65 | 68 | 69 | 76 | 77 | 78 | 79 | ) 80 | } 81 | -------------------------------------------------------------------------------- /nyro-chatbot/components/sidebar/items/chat/update-chat.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button" 2 | import { 3 | Dialog, 4 | DialogContent, 5 | DialogFooter, 6 | DialogHeader, 7 | DialogTitle, 8 | DialogTrigger 9 | } from "@/components/ui/dialog" 10 | import { Input } from "@/components/ui/input" 11 | import { Label } from "@/components/ui/label" 12 | import { NyroContext } from "@/context/context" 13 | import { updateChat } from "@/db/chats" 14 | import { Tables } from "@/supabase/types" 15 | import { IconEdit } from "@tabler/icons-react" 16 | import { FC, useContext, useRef, useState } from "react" 17 | 18 | interface UpdateChatProps { 19 | chat: Tables<"chats"> 20 | } 21 | 22 | export const UpdateChat: FC = ({ chat }) => { 23 | const { setChats } = useContext(NyroContext) 24 | 25 | const buttonRef = useRef(null) 26 | 27 | const [showChatDialog, setShowChatDialog] = useState(false) 28 | const [name, setName] = useState(chat.name) 29 | 30 | const handleUpdateChat = async (e: React.MouseEvent) => { 31 | const updatedChat = await updateChat(chat.id, { 32 | name 33 | }) 34 | setChats(prevState => 35 | prevState.map(c => (c.id === chat.id ? updatedChat : c)) 36 | ) 37 | 38 | setShowChatDialog(false) 39 | } 40 | 41 | const handleKeyDown = (e: React.KeyboardEvent) => { 42 | if (e.key === "Enter") { 43 | buttonRef.current?.click() 44 | } 45 | } 46 | 47 | return ( 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Edit Chat 56 | 57 | 58 |
59 | 60 | 61 | setName(e.target.value)} /> 62 |
63 | 64 | 65 | 68 | 69 | 72 | 73 |
74 |
75 | ) 76 | } 77 | -------------------------------------------------------------------------------- /nyro-chatbot/components/sidebar/items/folders/update-folder.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button" 2 | import { 3 | Dialog, 4 | DialogContent, 5 | DialogFooter, 6 | DialogHeader, 7 | DialogTitle, 8 | DialogTrigger 9 | } from "@/components/ui/dialog" 10 | import { Input } from "@/components/ui/input" 11 | import { Label } from "@/components/ui/label" 12 | import { NyroContext } from "@/context/context" 13 | import { updateFolder } from "@/db/folders" 14 | import { Tables } from "@/supabase/types" 15 | import { IconEdit } from "@tabler/icons-react" 16 | import { FC, useContext, useRef, useState } from "react" 17 | 18 | interface UpdateFolderProps { 19 | folder: Tables<"folders"> 20 | } 21 | 22 | export const UpdateFolder: FC = ({ folder }) => { 23 | const { setFolders } = useContext(NyroContext) 24 | 25 | const buttonRef = useRef(null) 26 | 27 | const [showFolderDialog, setShowFolderDialog] = useState(false) 28 | const [name, setName] = useState(folder.name) 29 | 30 | const handleUpdateFolder = async (e: React.MouseEvent) => { 31 | const updatedFolder = await updateFolder(folder.id, { 32 | name 33 | }) 34 | setFolders(prevState => 35 | prevState.map(c => (c.id === folder.id ? updatedFolder : c)) 36 | ) 37 | 38 | setShowFolderDialog(false) 39 | } 40 | 41 | const handleKeyDown = (e: React.KeyboardEvent) => { 42 | if (e.key === "Enter") { 43 | buttonRef.current?.click() 44 | } 45 | } 46 | 47 | return ( 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Edit Folder 56 | 57 | 58 |
59 | 60 | 61 | setName(e.target.value)} /> 62 |
63 | 64 | 65 | 68 | 69 | 72 | 73 |
74 |
75 | ) 76 | } 77 | -------------------------------------------------------------------------------- /nyro-chatbot/components/sidebar/items/prompts/create-prompt.tsx: -------------------------------------------------------------------------------- 1 | import { SidebarCreateItem } from "@/components/sidebar/items/all/sidebar-create-item" 2 | import { Input } from "@/components/ui/input" 3 | import { Label } from "@/components/ui/label" 4 | import { TextareaAutosize } from "@/components/ui/textarea-autosize" 5 | import { NyroContext } from "@/context/context" 6 | import { PROMPT_NAME_MAX } from "@/db/limits" 7 | import { TablesInsert } from "@/supabase/types" 8 | import { FC, useContext, useState } from "react" 9 | 10 | interface CreatePromptProps { 11 | isOpen: boolean 12 | onOpenChange: (isOpen: boolean) => void 13 | } 14 | 15 | export const CreatePrompt: FC = ({ 16 | isOpen, 17 | onOpenChange 18 | }) => { 19 | const { profile, selectedWorkspace } = useContext(NyroContext) 20 | const [isTyping, setIsTyping] = useState(false) 21 | const [name, setName] = useState("") 22 | const [content, setContent] = useState("") 23 | 24 | if (!profile) return null 25 | if (!selectedWorkspace) return null 26 | 27 | return ( 28 | 39 | } 40 | renderInputs={() => ( 41 | <> 42 |
43 | 44 | 45 | setName(e.target.value)} 49 | maxLength={PROMPT_NAME_MAX} 50 | onCompositionStart={() => setIsTyping(true)} 51 | onCompositionEnd={() => setIsTyping(false)} 52 | /> 53 |
54 | 55 |
56 | 57 | 58 | setIsTyping(true)} 65 | onCompositionEnd={() => setIsTyping(false)} 66 | /> 67 |
68 | 69 | )} 70 | /> 71 | ) 72 | } 73 | -------------------------------------------------------------------------------- /nyro-chatbot/components/sidebar/items/prompts/prompt-item.tsx: -------------------------------------------------------------------------------- 1 | import { Input } from "@/components/ui/input" 2 | import { Label } from "@/components/ui/label" 3 | import { TextareaAutosize } from "@/components/ui/textarea-autosize" 4 | import { PROMPT_NAME_MAX } from "@/db/limits" 5 | import { Tables } from "@/supabase/types" 6 | import { IconPencil } from "@tabler/icons-react" 7 | import { FC, useState } from "react" 8 | import { SidebarItem } from "../all/sidebar-display-item" 9 | 10 | interface PromptItemProps { 11 | prompt: Tables<"prompts"> 12 | } 13 | 14 | export const PromptItem: FC = ({ prompt }) => { 15 | const [name, setName] = useState(prompt.name) 16 | const [content, setContent] = useState(prompt.content) 17 | const [isTyping, setIsTyping] = useState(false) 18 | return ( 19 | } 24 | updateState={{ name, content }} 25 | renderInputs={() => ( 26 | <> 27 |
28 | 29 | 30 | setName(e.target.value)} 34 | maxLength={PROMPT_NAME_MAX} 35 | onCompositionStart={() => setIsTyping(true)} 36 | onCompositionEnd={() => setIsTyping(false)} 37 | /> 38 |
39 | 40 |
41 | 42 | 43 | setIsTyping(true)} 50 | onCompositionEnd={() => setIsTyping(false)} 51 | /> 52 |
53 | 54 | )} 55 | /> 56 | ) 57 | } 58 | -------------------------------------------------------------------------------- /nyro-chatbot/components/sidebar/sidebar-content.tsx: -------------------------------------------------------------------------------- 1 | import { Tables } from "@/supabase/types" 2 | import { ContentType, DataListType } from "@/types" 3 | import { FC, useState } from "react" 4 | import { SidebarCreateButtons } from "./sidebar-create-buttons" 5 | import { SidebarDataList } from "./sidebar-data-list" 6 | import { SidebarSearch } from "./sidebar-search" 7 | 8 | interface SidebarContentProps { 9 | contentType: ContentType 10 | data: DataListType 11 | folders: Tables<"folders">[] 12 | } 13 | 14 | export const SidebarContent: FC = ({ 15 | contentType, 16 | data, 17 | folders 18 | }) => { 19 | const [searchTerm, setSearchTerm] = useState("") 20 | 21 | const filteredData: any = data.filter(item => 22 | item.name.toLowerCase().includes(searchTerm.toLowerCase()) 23 | ) 24 | 25 | return ( 26 | // Subtract 50px for the height of the workspace settings 27 |
28 |
29 | 0} 32 | /> 33 |
34 | 35 |
36 | 41 |
42 | 43 | 48 |
49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /nyro-chatbot/components/sidebar/sidebar-search.tsx: -------------------------------------------------------------------------------- 1 | import { ContentType } from "@/types" 2 | import { FC } from "react" 3 | import { Input } from "../ui/input" 4 | 5 | interface SidebarSearchProps { 6 | contentType: ContentType 7 | searchTerm: string 8 | setSearchTerm: Function 9 | } 10 | 11 | export const SidebarSearch: FC = ({ 12 | contentType, 13 | searchTerm, 14 | setSearchTerm 15 | }) => { 16 | return ( 17 | setSearchTerm(e.target.value)} 21 | /> 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /nyro-chatbot/components/sidebar/sidebar-switch-item.tsx: -------------------------------------------------------------------------------- 1 | import { ContentType } from "@/types" 2 | import { FC } from "react" 3 | import { TabsTrigger } from "../ui/tabs" 4 | import { WithTooltip } from "../ui/with-tooltip" 5 | 6 | interface SidebarSwitchItemProps { 7 | contentType: ContentType 8 | icon: React.ReactNode 9 | onContentTypeChange: (contentType: ContentType) => void 10 | } 11 | 12 | export const SidebarSwitchItem: FC = ({ 13 | contentType, 14 | icon, 15 | onContentTypeChange 16 | }) => { 17 | return ( 18 | {contentType[0].toUpperCase() + contentType.substring(1)} 21 | } 22 | trigger={ 23 | onContentTypeChange(contentType as ContentType)} 27 | > 28 | {icon} 29 | 30 | } 31 | /> 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/accordion.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AccordionPrimitive from "@radix-ui/react-accordion" 5 | import { ChevronDown } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Accordion = AccordionPrimitive.Root 10 | 11 | const AccordionItem = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 20 | )) 21 | AccordionItem.displayName = "AccordionItem" 22 | 23 | const AccordionTrigger = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, children, ...props }, ref) => ( 27 | 28 | svg]:rotate-180", 32 | className 33 | )} 34 | {...props} 35 | > 36 | {children} 37 | 38 | 39 | 40 | )) 41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName 42 | 43 | const AccordionContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, children, ...props }, ref) => ( 47 | 52 |
{children}
53 |
54 | )) 55 | 56 | AccordionContent.displayName = AccordionPrimitive.Content.displayName 57 | 58 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } 59 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/advanced-settings.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Collapsible, 3 | CollapsibleContent, 4 | CollapsibleTrigger 5 | } from "@/components/ui/collapsible" 6 | import { IconChevronDown, IconChevronRight } from "@tabler/icons-react" 7 | import { FC, useState } from "react" 8 | 9 | interface AdvancedSettingsProps { 10 | children: React.ReactNode 11 | } 12 | 13 | export const AdvancedSettings: FC = ({ children }) => { 14 | const [isOpen, setIsOpen] = useState( 15 | false 16 | // localStorage.getItem("advanced-settings-open") === "true" 17 | ) 18 | 19 | const handleOpenChange = (isOpen: boolean) => { 20 | setIsOpen(isOpen) 21 | // localStorage.setItem("advanced-settings-open", String(isOpen)) 22 | } 23 | 24 | return ( 25 | 26 | 27 |
28 |
Advanced Settings
29 | {isOpen ? ( 30 | 31 | ) : ( 32 | 33 | )} 34 |
35 |
36 | 37 | {children} 38 |
39 | ) 40 | } 41 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const alertVariants = cva( 7 | "[&>svg]:text-foreground relative w-full rounded-lg border p-4 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg~*]:pl-7", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-background text-foreground", 12 | destructive: 13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive" 14 | } 15 | }, 16 | defaultVariants: { 17 | variant: "default" 18 | } 19 | } 20 | ) 21 | 22 | const Alert = React.forwardRef< 23 | HTMLDivElement, 24 | React.HTMLAttributes & VariantProps 25 | >(({ className, variant, ...props }, ref) => ( 26 |
32 | )) 33 | Alert.displayName = "Alert" 34 | 35 | const AlertTitle = React.forwardRef< 36 | HTMLParagraphElement, 37 | React.HTMLAttributes 38 | >(({ className, ...props }, ref) => ( 39 |
44 | )) 45 | AlertTitle.displayName = "AlertTitle" 46 | 47 | const AlertDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |
56 | )) 57 | AlertDescription.displayName = "AlertDescription" 58 | 59 | export { Alert, AlertTitle, AlertDescription } 60 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" 4 | 5 | const AspectRatio = AspectRatioPrimitive.Root 6 | 7 | export { AspectRatio } 8 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )) 21 | Avatar.displayName = AvatarPrimitive.Root.displayName 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )) 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )) 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 49 | 50 | export { Avatar, AvatarImage, AvatarFallback } 51 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "focus:ring-ring inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "bg-primary text-primary-foreground hover:bg-primary/80 border-transparent", 13 | secondary: 14 | "bg-secondary text-secondary-foreground hover:bg-secondary/80 border-transparent", 15 | destructive: 16 | "bg-destructive text-destructive-foreground hover:bg-destructive/80 border-transparent", 17 | outline: "text-foreground" 18 | } 19 | }, 20 | defaultVariants: { 21 | variant: "default" 22 | } 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/brand.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import Link from "next/link" 4 | import { FC } from "react" 5 | import { NyroSVG } from "../icons/nyro-svg" 6 | 7 | interface BrandProps { 8 | theme?: "dark" | "light" 9 | } 10 | 11 | export const Brand: FC = ({ theme = "dark" }) => { 12 | return ( 13 | 19 |
20 | 21 |
22 | 23 |
Nyro
24 | 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/button.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 | "ring-offset-background focus-visible:ring-ring inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors hover:opacity-50 focus-visible:outline-none focus-visible:ring-2 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-input bg-background hover:bg-accent hover:text-accent-foreground border", 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: "size-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 Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button" 45 | return ( 46 | 51 | ) 52 | } 53 | ) 54 | Button.displayName = "Button" 55 | 56 | export { Button, buttonVariants } 57 | -------------------------------------------------------------------------------- /nyro-chatbot/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 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

44 | )) 45 | CardTitle.displayName = "CardTitle" 46 | 47 | const CardDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |

56 | )) 57 | CardDescription.displayName = "CardDescription" 58 | 59 | const CardContent = React.forwardRef< 60 | HTMLDivElement, 61 | React.HTMLAttributes 62 | >(({ className, ...props }, ref) => ( 63 |

64 | )) 65 | CardContent.displayName = "CardContent" 66 | 67 | const CardFooter = React.forwardRef< 68 | HTMLDivElement, 69 | React.HTMLAttributes 70 | >(({ className, ...props }, ref) => ( 71 |
76 | )) 77 | CardFooter.displayName = "CardFooter" 78 | 79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 80 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/chat-app.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import React, { ReactNode, useContext } from 'react' 4 | import MenuBar from './menu-bar' 5 | import PeekBar from './peek-bar' 6 | import { NyroContext } from '@/context/context' 7 | 8 | interface ChatAppProps { 9 | children: ReactNode 10 | } 11 | 12 | const ChatApp: React.FC = ({ children }) => { 13 | const { 14 | appRef, 15 | otherRef, 16 | isRetracted, 17 | isTransparent, 18 | isPinned, 19 | handleRetract, 20 | handleExpand, 21 | handleTogglePin, 22 | handleMouseEnter, 23 | handleMouseMove, 24 | handleMouseLeave, 25 | handleMouseDown 26 | } = useContext(NyroContext); 27 | 28 | return ( 29 |
36 | {isRetracted ? ( 37 | 38 | ) : ( 39 | <> 40 |
44 | 49 |
50 | {children} 51 | 52 | )} 53 | 54 |
55 | ); 56 | } 57 | 58 | export default ChatApp; -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox" 5 | import { Check } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Checkbox = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => ( 13 | 21 | 24 | 25 | 26 | 27 | )) 28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName 29 | 30 | export { Checkbox } 31 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as CollapsiblePrimitive from "@radix-ui/react-collapsible" 4 | 5 | const Collapsible = CollapsiblePrimitive.Root 6 | 7 | const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger 8 | 9 | const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent 10 | 11 | export { Collapsible, CollapsibleTrigger, CollapsibleContent } 12 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/file-icon.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | IconFile, 3 | IconFileText, 4 | IconFileTypeCsv, 5 | IconFileTypeDocx, 6 | IconFileTypePdf, 7 | IconJson, 8 | IconMarkdown, 9 | IconPhoto 10 | } from "@tabler/icons-react" 11 | import { FC } from "react" 12 | 13 | interface FileIconProps { 14 | type: string 15 | size?: number 16 | } 17 | 18 | export const FileIcon: FC = ({ type, size = 32 }) => { 19 | if (type.includes("image")) { 20 | return 21 | } else if (type.includes("pdf")) { 22 | return 23 | } else if (type.includes("csv")) { 24 | return 25 | } else if (type.includes("docx")) { 26 | return 27 | } else if (type.includes("plain")) { 28 | return 29 | } else if (type.includes("json")) { 30 | return 31 | } else if (type.includes("markdown")) { 32 | return 33 | } else { 34 | return 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/file-preview.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | import { Tables } from "@/supabase/types" 3 | import { ChatFile, MessageImage } from "@/types" 4 | import { IconFileFilled } from "@tabler/icons-react" 5 | import Image from "next/image" 6 | import { FC } from "react" 7 | import { DrawingCanvas } from "../utility/drawing-canvas" 8 | import { Dialog, DialogContent } from "./dialog" 9 | 10 | interface FilePreviewProps { 11 | type: "image" | "file" | "file_item" 12 | item: ChatFile | MessageImage | Tables<"file_items"> 13 | isOpen: boolean 14 | onOpenChange: (isOpen: boolean) => void 15 | } 16 | 17 | export const FilePreview: FC = ({ 18 | type, 19 | item, 20 | isOpen, 21 | onOpenChange 22 | }) => { 23 | return ( 24 | 25 | 31 | {(() => { 32 | if (type === "image") { 33 | const imageItem = item as MessageImage 34 | 35 | return imageItem.file ? ( 36 | 37 | ) : ( 38 | File image 49 | ) 50 | } else if (type === "file_item") { 51 | const fileItem = item as Tables<"file_items"> 52 | return ( 53 |
54 |
{fileItem.content}
55 |
56 | ) 57 | } else if (type === "file") { 58 | return ( 59 |
60 | 61 |
62 | ) 63 | } 64 | })()} 65 |
66 |
67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/hover-card.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as HoverCardPrimitive from "@radix-ui/react-hover-card" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const HoverCard = HoverCardPrimitive.Root 9 | 10 | const HoverCardTrigger = HoverCardPrimitive.Trigger 11 | 12 | const HoverCardContent = React.forwardRef< 13 | React.ElementRef, 14 | React.ComponentPropsWithoutRef 15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 16 | 26 | )) 27 | HoverCardContent.displayName = HoverCardPrimitive.Content.displayName 28 | 29 | export { HoverCard, HoverCardTrigger, HoverCardContent } 30 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ) 21 | } 22 | ) 23 | Input.displayName = "Input" 24 | 25 | export { Input } 26 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as LabelPrimitive from "@radix-ui/react-label" 4 | import { cva, type VariantProps } from "class-variance-authority" 5 | import * as React from "react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-semibold leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/limit-display.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from "react" 2 | 3 | interface LimitDisplayProps { 4 | used: number 5 | limit: number 6 | } 7 | 8 | export const LimitDisplay: FC = ({ used, limit }) => { 9 | return ( 10 |
11 | {used}/{limit} 12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as PopoverPrimitive from "@radix-ui/react-popover" 5 | 6 | import { cn } from "@/lib/utils" 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 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/progress.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ProgressPrimitive from "@radix-ui/react-progress" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Progress = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, value, ...props }, ref) => ( 12 | 20 | 24 | 25 | )) 26 | Progress.displayName = ProgressPrimitive.Root.displayName 27 | 28 | export { Progress } 29 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/radio-group.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" 5 | import { Circle } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const RadioGroup = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => { 13 | return ( 14 | 19 | ) 20 | }) 21 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName 22 | 23 | const RadioGroupItem = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => { 27 | return ( 28 | 36 | 37 | 38 | 39 | 40 | ) 41 | }) 42 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName 43 | 44 | export { RadioGroup, RadioGroupItem } 45 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/screen-loader.tsx: -------------------------------------------------------------------------------- 1 | import { IconLoader2 } from "@tabler/icons-react" 2 | import { FC } from "react" 3 | 4 | interface ScreenLoaderProps {} 5 | 6 | export const ScreenLoader: FC = () => { 7 | return ( 8 |
9 | 10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const ScrollArea = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, children, ...props }, ref) => ( 12 | 17 | 18 | {children} 19 | 20 | 21 | 22 | 23 | )) 24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName 25 | 26 | const ScrollBar = React.forwardRef< 27 | React.ElementRef, 28 | React.ComponentPropsWithoutRef 29 | >(({ className, orientation = "vertical", ...props }, ref) => ( 30 | 43 | 44 | 45 | )) 46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName 47 | 48 | export { ScrollArea, ScrollBar } 49 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Separator = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >( 12 | ( 13 | { className, orientation = "horizontal", decorative = true, ...props }, 14 | ref 15 | ) => ( 16 | 27 | ) 28 | ) 29 | Separator.displayName = SeparatorPrimitive.Root.displayName 30 | 31 | export { Separator } 32 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/slider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as SliderPrimitive from "@radix-ui/react-slider" 4 | import * as React from "react" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Slider = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | 21 | 22 | 23 | 24 | 25 | )) 26 | Slider.displayName = SliderPrimitive.Root.displayName 27 | 28 | export { Slider } 29 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useTheme } from "next-themes" 4 | import { Toaster as Sonner } from "sonner" 5 | 6 | type ToasterProps = React.ComponentProps 7 | 8 | const Toaster = ({ ...props }: ToasterProps) => { 9 | const { theme = "system" } = useTheme() 10 | 11 | return ( 12 | 28 | ) 29 | } 30 | 31 | export { Toaster } 32 | -------------------------------------------------------------------------------- /nyro-chatbot/components/ui/submit-button.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import React from "react" 4 | import { useFormStatus } from "react-dom" 5 | import { Button, ButtonProps } from "./button" 6 | 7 | const SubmitButton = React.forwardRef( 8 | (props, ref) => { 9 | const { pending } = useFormStatus() 10 | 11 | return