From 66d8b8bb10b1839b7158897f029ff633065930fd Mon Sep 17 00:00:00 2001 From: pablonyx Date: Wed, 15 Jan 2025 15:09:49 -0800 Subject: [PATCH] Add chrome extension pages (#3629) --- web/next.config.js | 17 +- web/package-lock.json | 307 ++++++++++++++ web/package.json | 3 + .../app/admin/settings/AnonymousUserPath.tsx | 6 +- web/src/app/auth/login/LoginPage.tsx | 106 +++++ web/src/app/auth/login/SignInButton.tsx | 2 +- web/src/app/auth/login/page.tsx | 75 +--- web/src/app/chat/ChatIntro.tsx | 4 +- web/src/app/chat/ChatPage.tsx | 142 +++++-- web/src/app/chat/WrappedChat.tsx | 8 +- .../documentSidebar/ChatDocumentDisplay.tsx | 4 +- web/src/app/chat/input/ChatInputBar.tsx | 82 +++- .../app/chat/input/SimplifiedChatInputBar.tsx | 244 +++++++++++ web/src/app/chat/layout.tsx | 62 +++ .../chat/message/MemoizedTextComponents.tsx | 5 - web/src/app/chat/message/Messages.tsx | 14 +- web/src/app/chat/message/SearchSummary.tsx | 78 +++- web/src/app/chat/message/SkippedSearch.tsx | 31 +- web/src/app/chat/nrf/NRFPage.tsx | 384 ++++++++++++++++++ web/src/app/chat/nrf/interfaces.ts | 5 + web/src/app/chat/nrf/page.tsx | 20 + web/src/app/chat/page.tsx | 59 +-- web/src/app/chat/searchParams.ts | 1 + web/src/app/components/nrf/SettingsPanel.tsx | 179 ++++++++ .../app/components/nrf/ShortcutsDisplay.tsx | 46 +++ web/src/app/ee/Hori | 0 web/src/app/layout.tsx | 1 - .../components/assistants/AssistantIcon.tsx | 1 + .../chat_search/AssistantSelector.tsx | 8 +- web/src/components/chat_search/hooks.ts | 17 +- .../context/NRFPreferencesContext.tsx | 123 ++++++ web/src/components/extension/Shortcuts.tsx | 257 ++++++++++++ web/src/components/header/LogoWithText.tsx | 4 +- web/src/components/icons/icons.tsx | 21 + .../welcome/WelcomeModalWrapper.tsx | 1 - web/src/components/llm/ApiKeyModal.tsx | 23 +- web/src/components/popup/PopupFromQuery.tsx | 2 + .../search/filtering/FilterDropdown.tsx | 3 + .../filtering/HorizontalSourceSelector.tsx | 226 +++++++++++ .../components/search/results/Citation.tsx | 2 - web/src/components/tooltip/CustomTooltip.tsx | 12 +- web/src/components/ui/label.tsx | 26 ++ web/src/components/ui/radio-group.tsx | 44 ++ web/src/components/ui/switch.tsx | 9 +- web/src/lib/chat/fetchChatData.ts | 9 +- web/src/lib/chat/fetchSomeChatData.ts | 1 + web/src/lib/constants.ts | 7 +- web/src/lib/dateUtils.ts | 21 +- web/src/lib/extension/constants.ts | 33 ++ web/src/lib/extension/utils.ts | 51 +++ web/src/lib/hooks.ts | 105 ++++- web/src/lib/search/utils.ts | 7 +- web/src/lib/sources.ts | 23 ++ 53 files changed, 2639 insertions(+), 282 deletions(-) create mode 100644 web/src/app/auth/login/LoginPage.tsx create mode 100644 web/src/app/chat/input/SimplifiedChatInputBar.tsx create mode 100644 web/src/app/chat/layout.tsx create mode 100644 web/src/app/chat/nrf/NRFPage.tsx create mode 100644 web/src/app/chat/nrf/interfaces.ts create mode 100644 web/src/app/chat/nrf/page.tsx create mode 100644 web/src/app/components/nrf/SettingsPanel.tsx create mode 100644 web/src/app/components/nrf/ShortcutsDisplay.tsx create mode 100644 web/src/app/ee/Hori create mode 100644 web/src/components/context/NRFPreferencesContext.tsx create mode 100644 web/src/components/extension/Shortcuts.tsx create mode 100644 web/src/components/search/filtering/HorizontalSourceSelector.tsx create mode 100644 web/src/components/ui/label.tsx create mode 100644 web/src/components/ui/radio-group.tsx create mode 100644 web/src/lib/extension/constants.ts create mode 100644 web/src/lib/extension/utils.ts diff --git a/web/next.config.js b/web/next.config.js index 2877a6469..1a8d7a53a 100644 --- a/web/next.config.js +++ b/web/next.config.js @@ -13,7 +13,6 @@ const cspHeader = ` object-src 'none'; base-uri 'self'; form-action 'self'; - frame-ancestors 'none'; ${ process.env.NEXT_PUBLIC_CLOUD_ENABLED === "true" ? "upgrade-insecure-requests;" @@ -27,6 +26,17 @@ const nextConfig = { publicRuntimeConfig: { version, }, + images: { + // Used to fetch favicons + remotePatterns: [ + { + protocol: "https", + hostname: "www.google.com", + port: "", + pathname: "/s2/favicons/**", + }, + ], + }, async headers() { return [ { @@ -44,17 +54,12 @@ const nextConfig = { key: "Referrer-Policy", value: "strict-origin-when-cross-origin", }, - { - key: "X-Frame-Options", - value: "DENY", - }, { key: "X-Content-Type-Options", value: "nosniff", }, { key: "Permissions-Policy", - // Deny all permissions by default value: "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()", }, diff --git a/web/package-lock.json b/web/package-lock.json index d32345fe6..0d1eb688f 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -17,7 +17,9 @@ "@phosphor-icons/react": "^2.0.8", "@radix-ui/react-checkbox": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-popover": "^1.1.2", + "@radix-ui/react-radio-group": "^1.2.2", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", @@ -77,6 +79,7 @@ "devDependencies": { "@chromatic-com/playwright": "^0.10.0", "@tailwindcss/typography": "^0.5.10", + "@types/chrome": "^0.0.287", "chromatic": "^11.18.1", "eslint": "^8.48.0", "eslint-config-next": "^14.1.0", @@ -2912,6 +2915,85 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.1.tgz", + "integrity": "sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popover": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.2.tgz", @@ -3063,6 +3145,196 @@ } } }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.2.2.tgz", + "integrity": "sha512-E0MLLGfOP0l8P/NxgVzfXJ8w3Ch8cdO6UDzJfDChu4EJDy+/WdO5LqpdY8PYnCErkmZH3gZhDL1K7kQ41fAHuQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-roving-focus": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-previous": "1.1.0", + "@radix-ui/react-use-size": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-collection": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz", + "integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-presence": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", + "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz", + "integrity": "sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.0.tgz", @@ -4655,6 +4927,17 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@types/chrome": { + "version": "0.0.287", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.0.287.tgz", + "integrity": "sha512-wWhBNPNXZHwycHKNYnexUcpSbrihVZu++0rdp6GEk5ZgAglenLx+RwdEouh6FrHS0XQiOxSd62yaujM1OoQlZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/filesystem": "*", + "@types/har-format": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.36", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", @@ -4738,6 +5021,30 @@ "@types/estree": "*" } }, + "node_modules/@types/filesystem": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz", + "integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/filewriter": "*" + } + }, + "node_modules/@types/filewriter": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz", + "integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/har-format": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", + "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", diff --git a/web/package.json b/web/package.json index 4a52f11c9..3fdf30f85 100644 --- a/web/package.json +++ b/web/package.json @@ -19,7 +19,9 @@ "@phosphor-icons/react": "^2.0.8", "@radix-ui/react-checkbox": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-popover": "^1.1.2", + "@radix-ui/react-radio-group": "^1.2.2", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", @@ -79,6 +81,7 @@ "devDependencies": { "@chromatic-com/playwright": "^0.10.0", "@tailwindcss/typography": "^0.5.10", + "@types/chrome": "^0.0.287", "chromatic": "^11.18.1", "eslint": "^8.48.0", "eslint-config-next": "^14.1.0", diff --git a/web/src/app/admin/settings/AnonymousUserPath.tsx b/web/src/app/admin/settings/AnonymousUserPath.tsx index 699b70f25..b35a8d9aa 100644 --- a/web/src/app/admin/settings/AnonymousUserPath.tsx +++ b/web/src/app/admin/settings/AnonymousUserPath.tsx @@ -5,7 +5,7 @@ import { useState } from "react"; import { PopupSpec } from "@/components/admin/connectors/Popup"; import { Button } from "@/components/ui/button"; -import { NEXT_PUBLIC_CLOUD_DOMAIN } from "@/lib/constants"; +import { NEXT_PUBLIC_WEB_DOMAIN } from "@/lib/constants"; import { ClipboardIcon } from "@/components/icons/icons"; import { Input } from "@/components/ui/input"; import { ThreeDotsLoader } from "@/components/Loading"; @@ -118,7 +118,7 @@ export function AnonymousUserPath({
- {NEXT_PUBLIC_CLOUD_DOMAIN}/anonymous/ + {NEXT_PUBLIC_WEB_DOMAIN}/anonymous/ { navigator.clipboard.writeText( - `${NEXT_PUBLIC_CLOUD_DOMAIN}/anonymous/${anonymousUserPath}` + `${NEXT_PUBLIC_WEB_DOMAIN}/anonymous/${anonymousUserPath}` ); setPopup({ message: "Invite link copied!", diff --git a/web/src/app/auth/login/LoginPage.tsx b/web/src/app/auth/login/LoginPage.tsx new file mode 100644 index 000000000..cc10e5b74 --- /dev/null +++ b/web/src/app/auth/login/LoginPage.tsx @@ -0,0 +1,106 @@ +"use client"; + +import { AuthTypeMetadata } from "@/lib/userSS"; +import { LoginText } from "./LoginText"; +import Link from "next/link"; +import { SignInButton } from "./SignInButton"; +import { EmailPasswordForm } from "./EmailPasswordForm"; +import { NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED } from "@/lib/constants"; +import Title from "@/components/ui/title"; +import { useSendAuthRequiredMessage } from "@/lib/extension/utils"; + +export default function LoginPage({ + authUrl, + authTypeMetadata, + nextUrl, + searchParams, + showPageRedirect, +}: { + authUrl: string | null; + authTypeMetadata: AuthTypeMetadata | null; + nextUrl: string | null; + searchParams: + | { + [key: string]: string | string[] | undefined; + } + | undefined; + showPageRedirect?: boolean; +}) { + useSendAuthRequiredMessage(); + return ( +
+ {authUrl && authTypeMetadata && ( + <> +

+ +

+ + + + )} + + {authTypeMetadata?.authType === "cloud" && ( +
+
+
+ or +
+
+ + +
+ + Create an account + + + {NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED && ( + + Reset Password + + )} +
+
+ )} + + {authTypeMetadata?.authType === "basic" && ( + <> +
+ + <LoginText /> + +
+ +
+ + )} + {showPageRedirect && ( +

+ Don't have an account?{" "} + { + if (typeof window !== "undefined" && window.top) { + window.top.location.href = "/auth/signup"; + } else { + window.location.href = "/auth/signup"; + } + }} + className="text-link font-medium cursor-pointer" + > + Create an account + +

+ )} +
+ ); +} diff --git a/web/src/app/auth/login/SignInButton.tsx b/web/src/app/auth/login/SignInButton.tsx index b06f9bad7..b2d1a15be 100644 --- a/web/src/app/auth/login/SignInButton.tsx +++ b/web/src/app/auth/login/SignInButton.tsx @@ -46,7 +46,7 @@ export function SignInButton({ return ( {button} diff --git a/web/src/app/auth/login/page.tsx b/web/src/app/auth/login/page.tsx index 32ea1aa37..1d30a8242 100644 --- a/web/src/app/auth/login/page.tsx +++ b/web/src/app/auth/login/page.tsx @@ -7,18 +7,8 @@ import { AuthTypeMetadata, } from "@/lib/userSS"; import { redirect } from "next/navigation"; -import { SignInButton } from "./SignInButton"; -import { EmailPasswordForm } from "./EmailPasswordForm"; -import Title from "@/components/ui/title"; -import Text from "@/components/ui/text"; -import Link from "next/link"; -import { LoginText } from "./LoginText"; -import { getSecondsUntilExpiration } from "@/lib/time"; import AuthFlowContainer from "@/components/auth/AuthFlowContainer"; -import CardSection from "@/components/admin/CardSection"; -import { NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED } from "@/lib/constants"; -import { SettingsContext } from "@/components/settings/SettingsProvider"; -import { useContext } from "react"; +import LoginPage from "./LoginPage"; const Page = async (props: { searchParams?: Promise<{ [key: string]: string | string[] | undefined }>; @@ -49,13 +39,7 @@ const Page = async (props: { } // if user is already logged in, take them to the main app page - const secondsTillExpiration = getSecondsUntilExpiration(currentUser); - if ( - currentUser && - currentUser.is_active && - !currentUser.is_anonymous_user && - (secondsTillExpiration === null || secondsTillExpiration > 0) - ) { + if (currentUser && currentUser.is_active && !currentUser.is_anonymous_user) { if (authTypeMetadata?.requiresVerification && !currentUser.is_verified) { return redirect("/auth/waiting-on-verification"); } @@ -83,55 +67,12 @@ const Page = async (props: {
-
- {authUrl && authTypeMetadata && ( - <> -

- -

- - - - )} - - {authTypeMetadata?.authType === "cloud" && ( -
-
-
- or -
-
- - - -
- {NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED && ( - - Reset Password - - )} -
-
- )} - - {authTypeMetadata?.authType === "basic" && ( - <> -
- - <LoginText /> - -
- -
- - )} -
+
); diff --git a/web/src/app/chat/ChatIntro.tsx b/web/src/app/chat/ChatIntro.tsx index d86eb2315..1d9ef4353 100644 --- a/web/src/app/chat/ChatIntro.tsx +++ b/web/src/app/chat/ChatIntro.tsx @@ -14,7 +14,7 @@ export function ChatIntro({ selectedPersona }: { selectedPersona: Persona }) {
setHoveredAssistant(true)} onMouseLeave={() => setHoveredAssistant(false)} - className="p-4 scale-[.7] cursor-pointer border-dashed rounded-full flex border border-gray-300 border-2 border-dashed" + className="mobile:hidden p-4 scale-[.7] cursor-pointer border-dashed rounded-full flex border border-gray-300 border-2 border-dashed" >
-
+
{hoveredAssistant && ( )} diff --git a/web/src/app/chat/ChatPage.tsx b/web/src/app/chat/ChatPage.tsx index 0548bc3c5..af944f0b9 100644 --- a/web/src/app/chat/ChatPage.tsx +++ b/web/src/app/chat/ChatPage.tsx @@ -1,6 +1,6 @@ "use client"; -import { useRouter, useSearchParams } from "next/navigation"; +import { redirect, useRouter, useSearchParams } from "next/navigation"; import { BackendChatSession, BackendMessage, @@ -52,13 +52,11 @@ import { useLayoutEffect, useRef, useState, - useMemo, } from "react"; import { usePopup } from "@/components/admin/connectors/Popup"; import { SEARCH_PARAM_NAMES, shouldSubmitOnLoad } from "./searchParams"; import { useDocumentSelection } from "./useDocumentSelection"; import { LlmOverride, useFilters, useLlmOverride } from "@/lib/hooks"; -import { computeAvailableFilters } from "@/lib/filters"; import { ChatState, FeedbackType, RegenerationState } from "./types"; import { ChatFilters } from "./documentSidebar/ChatFilters"; import { OnyxInitializingLoader } from "@/components/OnyxInitializingLoader"; @@ -110,7 +108,11 @@ import AssistantBanner from "../../components/assistants/AssistantBanner"; import TextView from "@/components/chat_search/TextView"; import AssistantSelector from "@/components/chat_search/AssistantSelector"; import { Modal } from "@/components/Modal"; -import { createPostponedAbortSignal } from "next/dist/server/app-render/dynamic-rendering"; +import { useSendMessageToParent } from "@/lib/extension/utils"; +import { + CHROME_MESSAGE, + SUBMIT_MESSAGE_TYPES, +} from "@/lib/extension/constants"; const TEMP_USER_MESSAGE_ID = -1; const TEMP_ASSISTANT_MESSAGE_ID = -2; @@ -120,10 +122,12 @@ export function ChatPage({ toggle, documentSidebarInitialWidth, toggledSidebar, + firstMessage, }: { toggle: (toggled?: boolean) => void; documentSidebarInitialWidth?: number; toggledSidebar: boolean; + firstMessage?: string; }) { const router = useRouter(); const searchParams = useSearchParams(); @@ -140,6 +144,7 @@ export function ChatPage({ shouldShowWelcomeModal, refreshChatSessions, } = useChatContext(); + function useScreenSize() { const [screenSize, setScreenSize] = useState({ width: typeof window !== "undefined" ? window.innerWidth : 0, @@ -192,9 +197,6 @@ export function ChatPage({ const { user, isAdmin } = useUser(); const slackChatId = searchParams.get("slackChatId"); const existingChatIdRaw = searchParams.get("chatId"); - const [sendOnLoad, setSendOnLoad] = useState( - searchParams.get(SEARCH_PARAM_NAMES.SEND_ON_LOAD) - ); const modelVersionFromSearchParams = searchParams.get( SEARCH_PARAM_NAMES.STRUCTURED_MODEL @@ -210,24 +212,34 @@ export function ChatPage({ toggle(false); } }, [user]); - // Effect to handle sendOnLoad - useEffect(() => { - if (sendOnLoad) { - const newSearchParams = new URLSearchParams(searchParams.toString()); - newSearchParams.delete(SEARCH_PARAM_NAMES.SEND_ON_LOAD); - // Update the URL without the send-on-load parameter - router.replace(`?${newSearchParams.toString()}`, { scroll: false }); + const processSearchParamsAndSubmitMessage = (searchParamsString: string) => { + const newSearchParams = new URLSearchParams(searchParamsString); + const message = newSearchParams.get("user-prompt"); - // Update our local state to reflect the change - setSendOnLoad(null); + filterManager.buildFiltersFromQueryString( + newSearchParams.toString(), + availableSources, + documentSets.map((ds) => ds.name), + tags + ); - // If there's a message, submit it - if (message) { - onSubmit({ messageOverride: message }); - } + const fileDescriptorString = newSearchParams.get(SEARCH_PARAM_NAMES.FILES); + const overrideFileDescriptors: FileDescriptor[] = fileDescriptorString + ? JSON.parse(decodeURIComponent(fileDescriptorString)) + : []; + + newSearchParams.delete(SEARCH_PARAM_NAMES.SEND_ON_LOAD); + + router.replace(`?${newSearchParams.toString()}`, { scroll: false }); + + // If there's a message, submit it + if (message) { + console.log("SUBMITTING MESSAGE"); + setSubmittedMessage(message); + onSubmit({ messageOverride: message, overrideFileDescriptors }); } - }, [sendOnLoad, searchParams, router]); + }; const existingChatSessionId = existingChatIdRaw ? existingChatIdRaw : null; @@ -312,14 +324,6 @@ export function ChatPage({ const noAssistants = liveAssistant == null || liveAssistant == undefined; const availableSources = ccPairs.map((ccPair) => ccPair.source); - const [finalAvailableSources, finalAvailableDocumentSets] = - computeAvailableFilters({ - selectedPersona: availableAssistants.find( - (assistant) => assistant.id === liveAssistant?.id - ), - availableSources: availableSources, - availableDocumentSets: documentSets, - }); // always set the model override for the chat session, when an assistant, llm provider, or user preference exists useEffect(() => { @@ -399,8 +403,6 @@ export function ChatPage({ setIsReady(true); }, []); - // this is triggered every time the user switches which chat - // session they are using useEffect(() => { const priorChatSessionId = chatSessionIdRef.current; const loadedSessionId = loadedIdSessionRef.current; @@ -456,7 +458,6 @@ export function ChatPage({ } return; } - setIsReady(true); const shouldScrollToBottom = visibleRange.get(existingChatSessionId) === undefined || visibleRange.get(existingChatSessionId)?.end == 0; @@ -651,10 +652,10 @@ export function ChatPage({ currentMessageMap(completeMessageDetail) ); - const [submittedMessage, setSubmittedMessage] = useState(""); + const [submittedMessage, setSubmittedMessage] = useState(firstMessage || ""); const [chatState, setChatState] = useState>( - new Map([[chatSessionIdRef.current, "input"]]) + new Map([[chatSessionIdRef.current, firstMessage ? "loading" : "input"]]) ); const [regenerationState, setRegenerationState] = useState< @@ -798,6 +799,19 @@ export function ChatPage({ } }, [defaultAssistantId, availableAssistants, messageHistory.length]); + useEffect(() => { + if ( + submittedMessage && + currentSessionChatState === "loading" && + messageHistory.length == 0 + ) { + window.parent.postMessage( + { type: CHROME_MESSAGE.LOAD_NEW_CHAT_PAGE }, + "*" + ); + } + }, [submittedMessage, currentSessionChatState]); + const [ selectedDocuments, toggleDocumentSelection, @@ -997,12 +1011,32 @@ export function ChatPage({ } }, [chatSessionIdRef.current]); + const loadNewPageLogic = (event: MessageEvent) => { + if (event.data.type === SUBMIT_MESSAGE_TYPES.PAGE_CHANGE) { + try { + const url = new URL(event.data.href); + processSearchParamsAndSubmitMessage(url.searchParams.toString()); + } catch (error) { + console.error("Error parsing URL:", error); + } + } + }; + + // Equivalent to `loadNewPageLogic` useEffect(() => { - adjustDocumentSidebarWidth(); // Adjust the width on initial render - window.addEventListener("resize", adjustDocumentSidebarWidth); // Add resize event listener + if (searchParams.get(SEARCH_PARAM_NAMES.SEND_ON_LOAD)) { + processSearchParamsAndSubmitMessage(searchParams.toString()); + } + }, [searchParams, router]); + + useEffect(() => { + adjustDocumentSidebarWidth(); + window.addEventListener("resize", adjustDocumentSidebarWidth); + window.addEventListener("message", loadNewPageLogic); return () => { - window.removeEventListener("resize", adjustDocumentSidebarWidth); // Cleanup the event listener + window.removeEventListener("message", loadNewPageLogic); + window.removeEventListener("resize", adjustDocumentSidebarWidth); }; }, []); @@ -1078,6 +1112,7 @@ export function ChatPage({ alternativeAssistantOverride = null, modelOverRide, regenerationRequest, + overrideFileDescriptors, }: { messageIdToResend?: number; messageOverride?: string; @@ -1087,6 +1122,7 @@ export function ChatPage({ alternativeAssistantOverride?: Persona | null; modelOverRide?: LlmOverride; regenerationRequest?: RegenerationRequest | null; + overrideFileDescriptors?: FileDescriptor[]; } = {}) => { let frozenSessionId = currentSessionId(); updateCanContinue(false, frozenSessionId); @@ -1113,6 +1149,7 @@ export function ChatPage({ let currChatSessionId: string; const isNewSession = chatSessionIdRef.current === null; + const searchParamBasedChatSessionName = searchParams.get(SEARCH_PARAM_NAMES.TITLE) || null; @@ -1228,7 +1265,7 @@ export function ChatPage({ signal: controller.signal, // Add this line message: currMessage, alternateAssistantId: currentAssistantId, - fileDescriptors: currentMessageFiles, + fileDescriptors: overrideFileDescriptors || currentMessageFiles, parentMessageId: regenerationRequest?.parentMessage.messageId || lastSuccessfulMessageId, @@ -1815,6 +1852,7 @@ export function ChatPage({ end: 0, mostVisibleMessageId: null, }; + useSendMessageToParent(); useEffect(() => { if (noAssistants) { @@ -1889,6 +1927,7 @@ export function ChatPage({ handleSlackChatRedirect(); }, [searchParams, router]); + useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.metaKey || event.ctrlKey) { @@ -1957,6 +1996,10 @@ export function ChatPage({ }); }; } + if (!user) { + redirect("/auth/login"); + } + if (noAssistants) return ( <> @@ -2039,7 +2082,11 @@ export function ChatPage({ {retrievalEnabled && documentSidebarToggled && settings?.isMobile && (
- + setDocumentSidebarToggled(false)} + noPadding + noScroll + > + {({ getRootProps }) => (
{!settings?.isMobile && ( @@ -2327,7 +2378,8 @@ export function ChatPage({ {messageHistory.length === 0 && !isFetchingChatMessages && currentSessionChatState == "input" && - !loadingError && ( + !loadingError && + !submittedMessage && (
@@ -2344,7 +2396,7 @@ export function ChatPage({ currentSessionChatState == "input" && !loadingError && allAssistants.length > 1 && ( -
+
Recent Assistants @@ -2362,8 +2414,9 @@ export function ChatPage({ )}
)} { clearSelectedDocuments(); }} @@ -2786,7 +2840,6 @@ export function ChatPage({ } chatState={currentSessionChatState} stopGenerating={stopGenerating} - openModelSettings={() => setSettingsToggled(true)} selectedDocuments={selectedDocuments} // assistant stuff selectedAssistant={liveAssistant} @@ -2796,7 +2849,6 @@ export function ChatPage({ message={message} setMessage={setMessage} onSubmit={onSubmit} - filterManager={filterManager} files={currentMessageFiles} setFiles={setCurrentMessageFiles} toggleFilters={ diff --git a/web/src/app/chat/WrappedChat.tsx b/web/src/app/chat/WrappedChat.tsx index 6b48e4421..55d0f91c6 100644 --- a/web/src/app/chat/WrappedChat.tsx +++ b/web/src/app/chat/WrappedChat.tsx @@ -4,14 +4,20 @@ import FunctionalWrapper from "./shared_chat_search/FunctionalWrapper"; export default function WrappedChat({ initiallyToggled, + firstMessage, }: { initiallyToggled: boolean; + firstMessage?: string; }) { return ( ( - + )} /> ); diff --git a/web/src/app/chat/documentSidebar/ChatDocumentDisplay.tsx b/web/src/app/chat/documentSidebar/ChatDocumentDisplay.tsx index 8ee3c6ead..13c1e1b0d 100644 --- a/web/src/app/chat/documentSidebar/ChatDocumentDisplay.tsx +++ b/web/src/app/chat/documentSidebar/ChatDocumentDisplay.tsx @@ -79,7 +79,9 @@ export function ChatDocumentDisplay({ document.updated_at || Object.keys(document.metadata).length > 0; return (
void; +}) => ( +
+ Website favicon +

+ {new URL(url).hostname} +

+ onRemove(url)} + size={16} + className="text-text-400 hover:text-text-600 ml-auto cursor-pointer" + /> +
+); + +const SentUrlChip = ({ + url, + onRemove, + onClick, + title, +}: { + url: string; + onRemove: (url: string) => void; + onClick: () => void; + title: string; +}) => { + return ( + + ); +}; + interface ChatInputBarProps { removeDocs: () => void; - openModelSettings: () => void; showDocs: () => void; showConfigureAPIKey: () => void; selectedDocuments: OnyxDocument[]; @@ -45,7 +105,7 @@ interface ChatInputBarProps { setMessage: (message: string) => void; stopGenerating: () => void; onSubmit: () => void; - filterManager: FilterManager; + llmOverrideManager: LlmOverrideManager; chatState: ChatState; alternativeAssistant: Persona | null; // assistants @@ -61,7 +121,6 @@ interface ChatInputBarProps { export function ChatInputBar({ removeDocs, - openModelSettings, showDocs, showConfigureAPIKey, selectedDocuments, @@ -69,7 +128,6 @@ export function ChatInputBar({ setMessage, stopGenerating, onSubmit, - filterManager, chatState, // assistants @@ -408,7 +466,7 @@ export function ChatInputBar({ style={{ scrollbarWidth: "thin" }} role="textarea" aria-multiline - placeholder="Ask me anything.." + placeholder="Ask me anything..." value={message} onKeyDown={(event) => { if ( @@ -453,16 +511,6 @@ export function ChatInputBar({ onClick={toggleFilters} /> )} - {(filterManager.selectedSources.length > 0 || - filterManager.selectedDocumentSets.length > 0 || - filterManager.selectedTags.length > 0 || - filterManager.timeRange) && - toggleFilters && ( - - )}
diff --git a/web/src/app/chat/input/SimplifiedChatInputBar.tsx b/web/src/app/chat/input/SimplifiedChatInputBar.tsx new file mode 100644 index 000000000..20a5dae80 --- /dev/null +++ b/web/src/app/chat/input/SimplifiedChatInputBar.tsx @@ -0,0 +1,244 @@ +import React, { useEffect } from "react"; +import { FiPlusCircle } from "react-icons/fi"; +import { ChatInputOption } from "./ChatInputOption"; +import { FilterManager } from "@/lib/hooks"; +import { ChatFileType, FileDescriptor } from "../interfaces"; +import { + InputBarPreview, + InputBarPreviewImageProvider, +} from "../files/InputBarPreview"; +import { SendIcon } from "@/components/icons/icons"; +import { HorizontalSourceSelector } from "@/components/search/filtering/HorizontalSourceSelector"; +import { Tag } from "@/lib/types"; + +const MAX_INPUT_HEIGHT = 200; + +interface ChatInputBarProps { + message: string; + setMessage: (message: string) => void; + onSubmit: () => void; + files: FileDescriptor[]; + setFiles: (files: FileDescriptor[]) => void; + handleFileUpload: (files: File[]) => void; + textAreaRef: React.RefObject; + filterManager?: FilterManager; + existingSources: string[]; + availableDocumentSets: { name: string }[]; + availableTags: Tag[]; +} + +export function SimplifiedChatInputBar({ + message, + setMessage, + onSubmit, + files, + setFiles, + handleFileUpload, + textAreaRef, + filterManager, + existingSources, + availableDocumentSets, + availableTags, +}: ChatInputBarProps) { + useEffect(() => { + const textarea = textAreaRef.current; + if (textarea) { + textarea.style.height = "0px"; + textarea.style.height = `${Math.min( + textarea.scrollHeight, + MAX_INPUT_HEIGHT + )}px`; + } + }, [message, textAreaRef]); + + const handlePaste = (event: React.ClipboardEvent) => { + const items = event.clipboardData?.items; + if (items) { + const pastedFiles = []; + for (let i = 0; i < items.length; i++) { + if (items[i].kind === "file") { + const file = items[i].getAsFile(); + if (file) pastedFiles.push(file); + } + } + if (pastedFiles.length > 0) { + event.preventDefault(); + handleFileUpload(pastedFiles); + } + } + }; + + const handleInputChange = (event: React.ChangeEvent) => { + const text = event.target.value; + setMessage(text); + }; + + return ( +
+
+ {files.length > 0 && ( +
+
+ {files.map((file) => ( +
+ {file.type === ChatFileType.IMAGE ? ( + { + setFiles( + files.filter( + (fileInFilter) => fileInFilter.id !== file.id + ) + ); + }} + isUploading={file.isUploading || false} + /> + ) : ( + { + setFiles( + files.filter( + (fileInFilter) => fileInFilter.id !== file.id + ) + ); + }} + isUploading={file.isUploading || false} + /> + )} +
+ ))} +
+
+ )} + +