From de66f7adb263e92cf7f6ed0ba6dd1d56ffa836f4 Mon Sep 17 00:00:00 2001 From: pablodanswer Date: Sun, 1 Dec 2024 17:58:28 -0800 Subject: [PATCH] Updated chat flow (#3244) * proper no assistant typing + no assistant modal * updated chat flow * k * updates * update * k * clean up * fix mystery reorg * cleanup * update scroll * default * update logs * push fade * scroll nit * finalize tags * updates * k * various updates * viewport height update * source types update * clean up unused components * minor cleanup * cleanup complete * finalize changes * badge up * update filters * small nit * k * k * address comments * quick unification of icons * minor date range clarity * minor nit * k * update sidebar line * update for all screen sizes * k * k * k * k * rm shs * fix memoization * fix memoization * slack chat * k * k * build org --- ...2065484e6_add_auto_scroll_to_user_model.py | 27 + backend/danswer/auth/noauth_user.py | 4 +- backend/danswer/chat/process_message.py | 1 + backend/danswer/db/models.py | 1 + backend/danswer/seeding/personas.yaml | 2 +- backend/danswer/server/manage/models.py | 6 + backend/danswer/server/manage/users.py | 26 +- .../danswer/server/query_and_chat/models.py | 1 + backend/danswer/server/settings/api.py | 5 - backend/danswer/server/settings/models.py | 23 - .../danswer/server/enterprise_settings/api.py | 4 - backend/ee/danswer/server/seeding.py | 1 - web/@types/favicon-fetch.d.ts | 9 + web/package-lock.json | 1007 +++++------------ web/package.json | 6 +- web/src/app/admin/documents/sets/page.tsx | 2 +- web/src/app/admin/settings/SettingsForm.tsx | 58 +- web/src/app/admin/settings/interfaces.ts | 5 +- web/src/app/chat/ChatPage.tsx | 475 ++++++-- .../documentSidebar/ChatDocumentDisplay.tsx | 182 ++- .../app/chat/documentSidebar/ChatFilters.tsx | 186 +++ .../chat/documentSidebar/DocumentSidebar.tsx | 168 --- web/src/app/chat/input/ChatInputBar.tsx | 118 +- web/src/app/chat/lib.tsx | 11 +- .../chat/message/MemoizedTextComponents.tsx | 68 +- web/src/app/chat/message/Messages.tsx | 151 ++- web/src/app/chat/message/SearchSummary.tsx | 11 +- web/src/app/chat/modal/FeedbackModal.tsx | 2 +- .../app/chat/modal/SetDefaultModelModal.tsx | 35 +- web/src/app/chat/page.tsx | 4 + .../chat/sessionSidebar/HistorySidebar.tsx | 2 +- .../app/chat/shared_chat_search/Filters.tsx | 635 +++++++++++ .../app/chat/shared_chat_search/FixedLogo.tsx | 6 +- .../shared_chat_search/FunctionalWrapper.tsx | 112 +- .../chat/shared_chat_search/SearchFilters.tsx | 294 +++++ .../admin/whitelabeling/WhitelabelingForm.tsx | 2 + web/src/app/page.tsx | 12 +- web/src/app/search/WrappedSearch.tsx | 24 - web/src/app/search/page.tsx | 213 ---- web/src/components/InternetSearchIcon.tsx | 9 - web/src/components/MetadataBadge.tsx | 10 +- web/src/components/Modal.tsx | 24 +- web/src/components/SearchResultIcon.tsx | 65 ++ web/src/components/UserDropdown.tsx | 17 +- web/src/components/WebResultIcon.tsx | 16 + .../admin/connectors/AdminSidebar.tsx | 11 +- .../components/assistants/AssistantBanner.tsx | 8 +- .../components/assistants/AssistantCards.tsx | 104 +- .../chat_search/AssistantSelector.tsx | 344 ++++++ web/src/components/chat_search/Header.tsx | 54 +- .../chat_search/sources/SidebarSource.tsx | 0 .../chat_search/sources/SourceCard.tsx | 78 ++ web/src/components/context/ChatContext.tsx | 11 +- web/src/components/context/SearchContext.tsx | 2 +- web/src/components/icons/icons.tsx | 45 +- web/src/components/llm/LLMList.tsx | 45 +- web/src/components/search/DocumentDisplay.tsx | 54 +- .../search/DocumentUpdatedAtBadge.tsx | 15 +- web/src/components/search/SearchAnswer.tsx | 169 --- web/src/components/search/SearchBar.tsx | 96 +- .../search/SearchResultsDisplay.tsx | 301 ----- web/src/components/search/SearchSection.tsx | 856 -------------- .../components/search/SearchTypeSelector.tsx | 43 - .../components/search/filtering/Filters.tsx | 272 +++-- .../components/search/filtering/TagFilter.tsx | 18 +- .../components/search/results/Citation.tsx | 78 +- .../search/results/ResponseSection.tsx | 2 - web/src/components/settings/lib.ts | 4 +- web/src/components/tooltip/CustomTooltip.tsx | 2 +- web/src/components/ui/badge.tsx | 17 +- web/src/components/ui/checkbox.tsx | 30 + web/src/components/ui/drawer.tsx | 118 ++ web/src/components/ui/switch.tsx | 29 + web/src/components/ui/tooltip.tsx | 8 +- web/src/components/user/UserProvider.tsx | 32 + web/src/lib/search/interfaces.ts | 7 + web/src/lib/search/streamingQa.ts | 2 + web/src/lib/sources.ts | 2 +- web/src/lib/types.ts | 1 + web/tailwind-themes/tailwind.config.js | 1 + 80 files changed, 3441 insertions(+), 3458 deletions(-) create mode 100644 backend/alembic/versions/a8c2065484e6_add_auto_scroll_to_user_model.py create mode 100644 web/@types/favicon-fetch.d.ts create mode 100644 web/src/app/chat/documentSidebar/ChatFilters.tsx delete mode 100644 web/src/app/chat/documentSidebar/DocumentSidebar.tsx create mode 100644 web/src/app/chat/shared_chat_search/Filters.tsx create mode 100644 web/src/app/chat/shared_chat_search/SearchFilters.tsx delete mode 100644 web/src/app/search/WrappedSearch.tsx delete mode 100644 web/src/app/search/page.tsx delete mode 100644 web/src/components/InternetSearchIcon.tsx create mode 100644 web/src/components/SearchResultIcon.tsx create mode 100644 web/src/components/WebResultIcon.tsx create mode 100644 web/src/components/chat_search/AssistantSelector.tsx rename backend/DELETE => web/src/components/chat_search/sources/SidebarSource.tsx (100%) create mode 100644 web/src/components/chat_search/sources/SourceCard.tsx delete mode 100644 web/src/components/search/SearchAnswer.tsx delete mode 100644 web/src/components/search/SearchResultsDisplay.tsx delete mode 100644 web/src/components/search/SearchSection.tsx delete mode 100644 web/src/components/search/SearchTypeSelector.tsx create mode 100644 web/src/components/ui/checkbox.tsx create mode 100644 web/src/components/ui/drawer.tsx create mode 100644 web/src/components/ui/switch.tsx diff --git a/backend/alembic/versions/a8c2065484e6_add_auto_scroll_to_user_model.py b/backend/alembic/versions/a8c2065484e6_add_auto_scroll_to_user_model.py new file mode 100644 index 000000000..6cce89f3f --- /dev/null +++ b/backend/alembic/versions/a8c2065484e6_add_auto_scroll_to_user_model.py @@ -0,0 +1,27 @@ +"""add auto scroll to user model + +Revision ID: a8c2065484e6 +Revises: abe7378b8217 +Create Date: 2024-11-22 17:34:09.690295 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "a8c2065484e6" +down_revision = "abe7378b8217" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column( + "user", + sa.Column("auto_scroll", sa.Boolean(), nullable=True, server_default=None), + ) + + +def downgrade() -> None: + op.drop_column("user", "auto_scroll") diff --git a/backend/danswer/auth/noauth_user.py b/backend/danswer/auth/noauth_user.py index 9eb589dbb..c7e11cd45 100644 --- a/backend/danswer/auth/noauth_user.py +++ b/backend/danswer/auth/noauth_user.py @@ -23,7 +23,9 @@ def load_no_auth_user_preferences(store: KeyValueStore) -> UserPreferences: ) return UserPreferences(**preferences_data) except KvKeyNotFoundError: - return UserPreferences(chosen_assistants=None, default_model=None) + return UserPreferences( + chosen_assistants=None, default_model=None, auto_scroll=True + ) def fetch_no_auth_user(store: KeyValueStore) -> UserInfo: diff --git a/backend/danswer/chat/process_message.py b/backend/danswer/chat/process_message.py index 9048e21d6..e0056dec2 100644 --- a/backend/danswer/chat/process_message.py +++ b/backend/danswer/chat/process_message.py @@ -605,6 +605,7 @@ def stream_chat_message_objects( additional_headers=custom_tool_additional_headers, ), ) + tools: list[Tool] = [] for tool_list in tool_dict.values(): tools.extend(tool_list) diff --git a/backend/danswer/db/models.py b/backend/danswer/db/models.py index 2163aa5aa..bd8b76efa 100644 --- a/backend/danswer/db/models.py +++ b/backend/danswer/db/models.py @@ -126,6 +126,7 @@ class User(SQLAlchemyBaseUserTableUUID, Base): # if specified, controls the assistants that are shown to the user + their order # if not specified, all assistants are shown + auto_scroll: Mapped[bool] = mapped_column(Boolean, default=True) chosen_assistants: Mapped[list[int] | None] = mapped_column( postgresql.JSONB(), nullable=True, default=None ) diff --git a/backend/danswer/seeding/personas.yaml b/backend/danswer/seeding/personas.yaml index bafaf2d78..e628b32e6 100644 --- a/backend/danswer/seeding/personas.yaml +++ b/backend/danswer/seeding/personas.yaml @@ -5,7 +5,7 @@ personas: # this is for DanswerBot to use when tagged in a non-configured channel # Careful setting specific IDs, this won't autoincrement the next ID value for postgres - id: 0 - name: "Knowledge" + name: "Search" description: > Assistant with access to documents from your Connected Sources. # Default Prompt objects attached to the persona, see prompts.yaml diff --git a/backend/danswer/server/manage/models.py b/backend/danswer/server/manage/models.py index 9c2960741..0eb20df58 100644 --- a/backend/danswer/server/manage/models.py +++ b/backend/danswer/server/manage/models.py @@ -45,6 +45,7 @@ class UserPreferences(BaseModel): visible_assistants: list[int] = [] recent_assistants: list[int] | None = None default_model: str | None = None + auto_scroll: bool | None = None class UserInfo(BaseModel): @@ -79,6 +80,7 @@ class UserInfo(BaseModel): role=user.role, preferences=( UserPreferences( + auto_scroll=user.auto_scroll, chosen_assistants=user.chosen_assistants, default_model=user.default_model, hidden_assistants=user.hidden_assistants, @@ -128,6 +130,10 @@ class HiddenUpdateRequest(BaseModel): hidden: bool +class AutoScrollRequest(BaseModel): + auto_scroll: bool | None + + class SlackBotCreationRequest(BaseModel): name: str enabled: bool diff --git a/backend/danswer/server/manage/users.py b/backend/danswer/server/manage/users.py index 5e4197aaf..4ebda084c 100644 --- a/backend/danswer/server/manage/users.py +++ b/backend/danswer/server/manage/users.py @@ -52,6 +52,7 @@ from danswer.db.users import list_users from danswer.db.users import validate_user_role_update from danswer.key_value_store.factory import get_kv_store from danswer.server.manage.models import AllUsersResponse +from danswer.server.manage.models import AutoScrollRequest from danswer.server.manage.models import UserByEmail from danswer.server.manage.models import UserInfo from danswer.server.manage.models import UserPreferences @@ -497,7 +498,6 @@ def verify_user_logged_in( return fetch_no_auth_user(store) raise BasicAuthenticationError(detail="User Not Authenticated") - if user.oidc_expiry and user.oidc_expiry < datetime.now(timezone.utc): raise BasicAuthenticationError( detail="Access denied. User's OIDC token has expired.", @@ -581,6 +581,30 @@ def update_user_recent_assistants( db_session.commit() +@router.patch("/auto-scroll") +def update_user_auto_scroll( + request: AutoScrollRequest, + user: User | None = Depends(current_user), + db_session: Session = Depends(get_session), +) -> None: + if user is None: + if AUTH_TYPE == AuthType.DISABLED: + store = get_kv_store() + no_auth_user = fetch_no_auth_user(store) + no_auth_user.preferences.auto_scroll = request.auto_scroll + set_no_auth_user_preferences(store, no_auth_user.preferences) + return + else: + raise RuntimeError("This should never happen") + + db_session.execute( + update(User) + .where(User.id == user.id) # type: ignore + .values(auto_scroll=request.auto_scroll) + ) + db_session.commit() + + @router.patch("/user/default-model") def update_user_default_model( request: ChosenDefaultModelRequest, diff --git a/backend/danswer/server/query_and_chat/models.py b/backend/danswer/server/query_and_chat/models.py index ae6e651ff..bd1d4d34a 100644 --- a/backend/danswer/server/query_and_chat/models.py +++ b/backend/danswer/server/query_and_chat/models.py @@ -79,6 +79,7 @@ class CreateChatMessageRequest(ChunkContext): message: str # Files that we should attach to this message file_descriptors: list[FileDescriptor] + # If no prompt provided, uses the largest prompt of the chat session # but really this should be explicitly specified, only in the simplified APIs is this inferred # Use prompt_id 0 to use the system default prompt which is Answer-Question diff --git a/backend/danswer/server/settings/api.py b/backend/danswer/server/settings/api.py index 4f598a183..c453c2fb5 100644 --- a/backend/danswer/server/settings/api.py +++ b/backend/danswer/server/settings/api.py @@ -2,7 +2,6 @@ from typing import cast from fastapi import APIRouter from fastapi import Depends -from fastapi import HTTPException from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.orm import Session @@ -38,10 +37,6 @@ basic_router = APIRouter(prefix="/settings") def put_settings( settings: Settings, _: User | None = Depends(current_admin_user) ) -> None: - try: - settings.check_validity() - except ValueError as e: - raise HTTPException(status_code=400, detail=str(e)) store_settings(settings) diff --git a/backend/danswer/server/settings/models.py b/backend/danswer/server/settings/models.py index af9359550..55571536d 100644 --- a/backend/danswer/server/settings/models.py +++ b/backend/danswer/server/settings/models.py @@ -41,33 +41,10 @@ class Notification(BaseModel): class Settings(BaseModel): """General settings""" - chat_page_enabled: bool = True - search_page_enabled: bool = True - default_page: PageType = PageType.SEARCH maximum_chat_retention_days: int | None = None gpu_enabled: bool | None = None product_gating: GatingType = GatingType.NONE - def check_validity(self) -> None: - chat_page_enabled = self.chat_page_enabled - search_page_enabled = self.search_page_enabled - default_page = self.default_page - - if chat_page_enabled is False and search_page_enabled is False: - raise ValueError( - "One of `search_page_enabled` and `chat_page_enabled` must be True." - ) - - if default_page == PageType.CHAT and chat_page_enabled is False: - raise ValueError( - "The default page cannot be 'chat' if the chat page is disabled." - ) - - if default_page == PageType.SEARCH and search_page_enabled is False: - raise ValueError( - "The default page cannot be 'search' if the search page is disabled." - ) - class UserSettings(Settings): notifications: list[Notification] diff --git a/backend/ee/danswer/server/enterprise_settings/api.py b/backend/ee/danswer/server/enterprise_settings/api.py index 385adcf68..272d8bf93 100644 --- a/backend/ee/danswer/server/enterprise_settings/api.py +++ b/backend/ee/danswer/server/enterprise_settings/api.py @@ -113,10 +113,6 @@ async def refresh_access_token( def put_settings( settings: EnterpriseSettings, _: User | None = Depends(current_admin_user) ) -> None: - try: - settings.check_validity() - except ValueError as e: - raise HTTPException(status_code=400, detail=str(e)) store_settings(settings) diff --git a/backend/ee/danswer/server/seeding.py b/backend/ee/danswer/server/seeding.py index 7aa873792..b4920815a 100644 --- a/backend/ee/danswer/server/seeding.py +++ b/backend/ee/danswer/server/seeding.py @@ -157,7 +157,6 @@ def _seed_personas(db_session: Session, personas: list[CreatePersonaRequest]) -> def _seed_settings(settings: Settings) -> None: logger.notice("Seeding Settings") try: - settings.check_validity() store_base_settings(settings) logger.notice("Successfully seeded Settings") except ValueError as e: diff --git a/web/@types/favicon-fetch.d.ts b/web/@types/favicon-fetch.d.ts new file mode 100644 index 000000000..9b4d38319 --- /dev/null +++ b/web/@types/favicon-fetch.d.ts @@ -0,0 +1,9 @@ +declare module "favicon-fetch" { + interface FaviconFetchOptions { + uri: string; + } + + function faviconFetch(options: FaviconFetchOptions): string | null; + + export default faviconFetch; +} diff --git a/web/package-lock.json b/web/package-lock.json index 8315feea2..0696685da 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -15,11 +15,13 @@ "@headlessui/react": "^2.2.0", "@headlessui/tailwindcss": "^0.2.1", "@phosphor-icons/react": "^2.0.8", - "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.3", "@sentry/nextjs": "^8.34.0", @@ -35,6 +37,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "date-fns": "^3.6.0", + "favicon-fetch": "^1.0.0", "formik": "^2.2.9", "js-cookie": "^3.0.5", "lodash": "^4.17.21", @@ -65,6 +68,7 @@ "tailwindcss-animate": "^1.0.7", "typescript": "5.0.3", "uuid": "^9.0.1", + "vaul": "^1.1.1", "yup": "^1.4.0" }, "devDependencies": { @@ -2632,12 +2636,10 @@ "license": "MIT" }, "node_modules/@radix-ui/primitive": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz", - "integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==", - "dependencies": { - "@babel/runtime": "^7.13.10" - } + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", + "license": "MIT" }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.0", @@ -2661,6 +2663,51 @@ } } }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.2.tgz", + "integrity": "sha512-/i0fl686zaJbDQLNKrkCbMyDm6FQMt4jg323k7HuqitoANm9sE23Ql8yOK3Wusk34HSLKDChhMux05FnP6KUkw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@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-checkbox/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-collection": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", @@ -2715,31 +2762,31 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz", - "integrity": "sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz", + "integrity": "sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-context": "1.0.1", - "@radix-ui/react-dismissable-layer": "1.0.5", - "@radix-ui/react-focus-guards": "1.0.1", - "@radix-ui/react-focus-scope": "1.0.4", - "@radix-ui/react-id": "1.0.1", - "@radix-ui/react-portal": "1.0.4", - "@radix-ui/react-presence": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-slot": "1.0.2", - "@radix-ui/react-use-controllable-state": "1.0.1", + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.5" + "react-remove-scroll": "2.6.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" + "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": { @@ -2750,438 +2797,14 @@ } } }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-compose-refs": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", - "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz", - "integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz", - "integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/primitive": "1.0.1", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1", - "@radix-ui/react-use-escape-keydown": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", - "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz", - "integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-callback-ref": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", - "integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-scope": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz", - "integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-primitive": "1.0.3", - "@radix-ui/react-use-callback-ref": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", - "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-id": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", - "integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-layout-effect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-id/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz", - "integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", - "integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-primitive": "1.0.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz", - "integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1", - "@radix-ui/react-use-layout-effect": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence/node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz", - "integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", - "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-slot": "1.0.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", - "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "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", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-compose-refs": "1.0.1" - }, "peerDependencies": { "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz", - "integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==", - "dependencies": { - "@babel/runtime": "^7.13.10", - "@radix-ui/react-use-callback-ref": "1.0.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-controllable-state/node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", - "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", - "dependencies": { - "@babel/runtime": "^7.13.10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", - "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", - "dependencies": { - "react-remove-scroll-bar": "^2.3.3", - "react-style-singleton": "^2.2.1", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.0", - "use-sidecar": "^1.1.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll/node_modules/react-remove-scroll-bar": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", - "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", - "dependencies": { - "react-style-singleton": "^2.2.1", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll/node_modules/react-style-singleton": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", - "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", - "dependencies": { - "get-nonce": "^1.0.0", - "invariant": "^2.2.4", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll/node_modules/use-callback-ref": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", - "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll/node_modules/use-sidecar": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", - "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -3230,10 +2853,20 @@ } } }, - "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", + "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", + "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-focus-scope": { "version": "1.1.0", @@ -3313,12 +2946,6 @@ } } }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", - "license": "MIT" - }, "node_modules/@radix-ui/react-popover/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", @@ -3334,130 +2961,6 @@ } } }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", - "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", - "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-popover/node_modules/react-remove-scroll": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", - "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==", - "license": "MIT", - "dependencies": { - "react-remove-scroll-bar": "^2.3.6", - "react-style-singleton": "^2.2.1", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.0", - "use-sidecar": "^1.1.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/react-remove-scroll/node_modules/react-remove-scroll-bar": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", - "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", - "dependencies": { - "react-style-singleton": "^2.2.1", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/react-remove-scroll/node_modules/react-style-singleton": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", - "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", - "dependencies": { - "get-nonce": "^1.0.0", - "invariant": "^2.2.4", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/react-remove-scroll/node_modules/use-callback-ref": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", - "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/react-remove-scroll/node_modules/use-sidecar": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", - "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-popper": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", @@ -3587,11 +3090,6 @@ } } }, - "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" - }, "node_modules/@radix-ui/react-select": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.2.tgz", @@ -3635,12 +3133,6 @@ } } }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", - "license": "MIT" - }, "node_modules/@radix-ui/react-select/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", @@ -3656,130 +3148,6 @@ } } }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", - "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", - "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-select/node_modules/react-remove-scroll": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", - "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==", - "license": "MIT", - "dependencies": { - "react-remove-scroll-bar": "^2.3.6", - "react-style-singleton": "^2.2.1", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.0", - "use-sidecar": "^1.1.2" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/react-remove-scroll/node_modules/react-remove-scroll-bar": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", - "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", - "dependencies": { - "react-style-singleton": "^2.2.1", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/react-remove-scroll/node_modules/react-style-singleton": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", - "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", - "dependencies": { - "get-nonce": "^1.0.0", - "invariant": "^2.2.4", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/react-remove-scroll/node_modules/use-callback-ref": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", - "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/react-remove-scroll/node_modules/use-sidecar": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", - "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "node_modules/@radix-ui/react-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.0.tgz", @@ -3821,6 +3189,50 @@ } } }, + "node_modules/@radix-ui/react-switch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.1.1.tgz", + "integrity": "sha512-diPqDDoBcZPSicYoMWdWx+bCPuTRH4QSp9J+65IvtdS0Kuzt67bI6n32vCj8q6NZmYW/ah+2orOtMwcX5eQwIg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@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-switch/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-tabs": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.1.tgz", @@ -3851,12 +3263,6 @@ } } }, - "node_modules/@radix-ui/react-tabs/node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", - "license": "MIT" - }, "node_modules/@radix-ui/react-tabs/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", @@ -3906,12 +3312,6 @@ } } }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", - "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==", - "license": "MIT" - }, "node_modules/@radix-ui/react-tooltip/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", @@ -7201,7 +6601,8 @@ "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" }, "node_modules/devlop": { "version": "1.1.0", @@ -8184,6 +7585,12 @@ "reusify": "^1.0.4" } }, + "node_modules/favicon-fetch": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/favicon-fetch/-/favicon-fetch-1.0.0.tgz", + "integrity": "sha512-qEbMwsKBebUGo/JpTyeE5aBus5nTsIcYV7qRd5hxGWA3wOrp67aKXBrH3O23tYkNjnOThTyw9TaUrtWwOe3Y1w==", + "license": "MIT" + }, "node_modules/fflate": { "version": "0.4.8", "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz", @@ -8467,6 +7874,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", "engines": { "node": ">=6" } @@ -9152,6 +8560,7 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", "dependencies": { "loose-envify": "^1.0.0" } @@ -14599,6 +14008,53 @@ "react": ">=18" } }, + "node_modules/react-remove-scroll": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", + "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.6", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", + "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-select": { "version": "5.8.0", "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.8.0.tgz", @@ -14678,6 +14134,29 @@ } } }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -16358,6 +15837,49 @@ "dev": true, "license": "MIT" }, + "node_modules/use-callback-ref": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", + "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -16396,6 +15918,19 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/vaul": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.1.tgz", + "integrity": "sha512-+ejzF6ffQKPcfgS7uOrGn017g39F8SO4yLPXbBhpC7a0H+oPqPna8f1BUfXaz8eU4+pxbQcmjxW+jWBSbxjaFg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0" + } + }, "node_modules/vfile": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz", diff --git a/web/package.json b/web/package.json index c36da3633..e2eec0990 100644 --- a/web/package.json +++ b/web/package.json @@ -17,11 +17,13 @@ "@headlessui/react": "^2.2.0", "@headlessui/tailwindcss": "^0.2.1", "@phosphor-icons/react": "^2.0.8", - "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.3", "@sentry/nextjs": "^8.34.0", @@ -37,6 +39,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "date-fns": "^3.6.0", + "favicon-fetch": "^1.0.0", "formik": "^2.2.9", "js-cookie": "^3.0.5", "lodash": "^4.17.21", @@ -67,6 +70,7 @@ "tailwindcss-animate": "^1.0.7", "typescript": "5.0.3", "uuid": "^9.0.1", + "vaul": "^1.1.1", "yup": "^1.4.0" }, "devDependencies": { diff --git a/web/src/app/admin/documents/sets/page.tsx b/web/src/app/admin/documents/sets/page.tsx index c0123bb31..af4ba8532 100644 --- a/web/src/app/admin/documents/sets/page.tsx +++ b/web/src/app/admin/documents/sets/page.tsx @@ -83,7 +83,7 @@ const EditRow = ({ {!documentSet.is_up_to_date && ( - +
Cannot update while syncing! Wait for the sync to finish, then diff --git a/web/src/app/admin/settings/SettingsForm.tsx b/web/src/app/admin/settings/SettingsForm.tsx index f00e4d978..5e2eb0033 100644 --- a/web/src/app/admin/settings/SettingsForm.tsx +++ b/web/src/app/admin/settings/SettingsForm.tsx @@ -175,29 +175,6 @@ export function SettingsForm() { { fieldName, newValue: checked }, ]; - // If we're disabling a page, check if we need to update the default page - if ( - !checked && - (fieldName === "search_page_enabled" || fieldName === "chat_page_enabled") - ) { - const otherPageField = - fieldName === "search_page_enabled" - ? "chat_page_enabled" - : "search_page_enabled"; - const otherPageEnabled = settings && settings[otherPageField]; - - if ( - otherPageEnabled && - settings?.default_page === - (fieldName === "search_page_enabled" ? "search" : "chat") - ) { - updates.push({ - fieldName: "default_page", - newValue: fieldName === "search_page_enabled" ? "chat" : "search", - }); - } - } - updateSettingField(updates); } @@ -218,42 +195,17 @@ export function SettingsForm() { return (
{popup} - Page Visibility + Workspace Settings - handleToggleSettingsField("search_page_enabled", e.target.checked) + handleToggleSettingsField("auto_scroll", e.target.checked) } /> - - handleToggleSettingsField("chat_page_enabled", e.target.checked) - } - /> - - { - value && - updateSettingField([ - { fieldName: "default_page", newValue: value }, - ]); - }} - /> - {isEnterpriseEnabled && ( <> Chat Settings diff --git a/web/src/app/admin/settings/interfaces.ts b/web/src/app/admin/settings/interfaces.ts index 38959fc8c..32ce1d010 100644 --- a/web/src/app/admin/settings/interfaces.ts +++ b/web/src/app/admin/settings/interfaces.ts @@ -5,14 +5,12 @@ export enum GatingType { } export interface Settings { - chat_page_enabled: boolean; - search_page_enabled: boolean; - default_page: "search" | "chat"; maximum_chat_retention_days: number | null; notifications: Notification[]; needs_reindexing: boolean; gpu_enabled: boolean; product_gating: GatingType; + auto_scroll: boolean; } export enum NotificationType { @@ -54,6 +52,7 @@ export interface EnterpriseSettings { custom_popup_header: string | null; custom_popup_content: string | null; enable_consent_screen: boolean | null; + auto_scroll: boolean; } export interface CombinedSettings { diff --git a/web/src/app/chat/ChatPage.tsx b/web/src/app/chat/ChatPage.tsx index 94f336ba8..634dc0624 100644 --- a/web/src/app/chat/ChatPage.tsx +++ b/web/src/app/chat/ChatPage.tsx @@ -8,7 +8,6 @@ import { ChatFileType, ChatSession, ChatSessionSharedStatus, - DocumentsResponse, FileDescriptor, FileChatDisplay, Message, @@ -60,7 +59,7 @@ import { useDocumentSelection } from "./useDocumentSelection"; import { LlmOverride, useFilters, useLlmOverride } from "@/lib/hooks"; import { computeAvailableFilters } from "@/lib/filters"; import { ChatState, FeedbackType, RegenerationState } from "./types"; -import { DocumentSidebar } from "./documentSidebar/DocumentSidebar"; +import { ChatFilters } from "./documentSidebar/ChatFilters"; import { DanswerInitializingLoader } from "@/components/DanswerInitializingLoader"; import { FeedbackModal } from "./modal/FeedbackModal"; import { ShareChatSessionModal } from "./modal/ShareChatSessionModal"; @@ -71,6 +70,7 @@ import { StarterMessages } from "../../components/assistants/StarterMessage"; import { AnswerPiecePacket, DanswerDocument, + FinalContextDocs, StreamStopInfo, StreamStopReason, } from "@/lib/search/interfaces"; @@ -105,14 +105,9 @@ import BlurBackground from "./shared_chat_search/BlurBackground"; import { NoAssistantModal } from "@/components/modals/NoAssistantModal"; import { useAssistants } from "@/components/context/AssistantsContext"; import { Separator } from "@/components/ui/separator"; -import { - Card, - CardContent, - CardDescription, - CardHeader, -} from "@/components/ui/card"; -import { AssistantIcon } from "@/components/assistants/AssistantIcon"; import AssistantBanner from "../../components/assistants/AssistantBanner"; +import AssistantSelector from "@/components/chat_search/AssistantSelector"; +import { Modal } from "@/components/Modal"; const TEMP_USER_MESSAGE_ID = -1; const TEMP_ASSISTANT_MESSAGE_ID = -2; @@ -132,8 +127,9 @@ export function ChatPage({ const { chatSessions, - availableSources, - availableDocumentSets, + ccPairs, + tags, + documentSets, llmProviders, folders, openedFolders, @@ -142,6 +138,36 @@ export function ChatPage({ shouldShowWelcomeModal, refreshChatSessions, } = useChatContext(); + function useScreenSize() { + const [screenSize, setScreenSize] = useState({ + width: typeof window !== "undefined" ? window.innerWidth : 0, + height: typeof window !== "undefined" ? window.innerHeight : 0, + }); + + useEffect(() => { + const handleResize = () => { + setScreenSize({ + width: window.innerWidth, + height: window.innerHeight, + }); + }; + + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); + + return screenSize; + } + + const { height: screenHeight } = useScreenSize(); + + const getContainerHeight = () => { + if (autoScrollEnabled) return undefined; + + if (screenHeight < 600) return "20vh"; + if (screenHeight < 1200) return "30vh"; + return "40vh"; + }; // handle redirect if chat page is disabled // NOTE: this must be done here, in a client component since @@ -149,9 +175,11 @@ export function ChatPage({ // available in server-side components const settings = useContext(SettingsContext); const enterpriseSettings = settings?.enterpriseSettings; - if (settings?.settings?.chat_page_enabled === false) { - router.push("/search"); - } + + const [documentSidebarToggled, setDocumentSidebarToggled] = useState(false); + const [filtersToggled, setFiltersToggled] = useState(false); + + const [userSettingsToggled, setUserSettingsToggled] = useState(false); const { assistants: availableAssistants, finalAssistants } = useAssistants(); @@ -159,16 +187,13 @@ export function ChatPage({ !shouldShowWelcomeModal ); - const { user, isAdmin, isLoadingUser, refreshUser } = useUser(); - + const { user, isAdmin, isLoadingUser } = useUser(); const slackChatId = searchParams.get("slackChatId"); - const existingChatIdRaw = searchParams.get("chatId"); const [sendOnLoad, setSendOnLoad] = useState( searchParams.get(SEARCH_PARAM_NAMES.SEND_ON_LOAD) ); - const currentPersonaId = searchParams.get(SEARCH_PARAM_NAMES.PERSONA_ID); const modelVersionFromSearchParams = searchParams.get( SEARCH_PARAM_NAMES.STRUCTURED_MODEL ); @@ -261,7 +286,7 @@ export function ChatPage({ refreshRecentAssistants, } = useAssistants(); - const liveAssistant = + const liveAssistant: Persona | undefined = alternativeAssistant || selectedAssistant || recentAssistants[0] || @@ -269,8 +294,20 @@ export function ChatPage({ availableAssistants[0]; 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(() => { + if (noAssistants) return; const personaDefault = getLLMProviderOverrideForPersona( liveAssistant, llmProviders @@ -357,9 +394,7 @@ export function ChatPage({ textAreaRef.current?.focus(); // only clear things if we're going from one chat session to another - const isChatSessionSwitch = - chatSessionIdRef.current !== null && - existingChatSessionId !== priorChatSessionId; + const isChatSessionSwitch = existingChatSessionId !== priorChatSessionId; if (isChatSessionSwitch) { // de-select documents clearSelectedDocuments(); @@ -449,9 +484,9 @@ export function ChatPage({ } if (shouldScrollToBottom) { - if (!hasPerformedInitialScroll) { + if (!hasPerformedInitialScroll && autoScrollEnabled) { clientScrollToBottom(); - } else if (isChatSessionSwitch) { + } else if (isChatSessionSwitch && autoScrollEnabled) { clientScrollToBottom(true); } } @@ -759,7 +794,7 @@ export function ChatPage({ useEffect(() => { async function fetchMaxTokens() { const response = await fetch( - `/api/chat/max-selected-document-tokens?persona_id=${liveAssistant.id}` + `/api/chat/max-selected-document-tokens?persona_id=${liveAssistant?.id}` ); if (response.ok) { const maxTokens = (await response.json()).max_tokens as number; @@ -833,11 +868,13 @@ export function ChatPage({ 0 )}px`; - scrollableDivRef?.current.scrollBy({ - left: 0, - top: Math.max(heightDifference, 0), - behavior: "smooth", - }); + if (autoScrollEnabled) { + scrollableDivRef?.current.scrollBy({ + left: 0, + top: Math.max(heightDifference, 0), + behavior: "smooth", + }); + } } previousHeight.current = newHeight; } @@ -884,6 +921,7 @@ export function ChatPage({ endDivRef.current.scrollIntoView({ behavior: fast ? "auto" : "smooth", }); + setHasPerformedInitialScroll(true); } }, 50); @@ -1035,7 +1073,9 @@ export function ChatPage({ } setAlternativeGeneratingAssistant(alternativeAssistantOverride); + clientScrollToBottom(); + let currChatSessionId: string; const isNewSession = chatSessionIdRef.current === null; const searchParamBasedChatSessionName = @@ -1281,8 +1321,8 @@ export function ChatPage({ if (Object.hasOwn(packet, "answer_piece")) { answer += (packet as AnswerPiecePacket).answer_piece; - } else if (Object.hasOwn(packet, "top_documents")) { - documents = (packet as DocumentsResponse).top_documents; + } else if (Object.hasOwn(packet, "final_context_docs")) { + documents = (packet as FinalContextDocs).final_context_docs; retrievalType = RetrievalType.Search; if (documents && documents.length > 0) { // point to the latest message (we don't know the messageId yet, which is why @@ -1379,8 +1419,7 @@ export function ChatPage({ type: error ? "error" : "assistant", retrievalType, query: finalMessage?.rephrased_query || query, - documents: - finalMessage?.context_docs?.top_documents || documents, + documents: documents, citations: finalMessage?.citations || {}, files: finalMessage?.files || aiMessageImages || [], toolCall: finalMessage?.tool_call || toolCall, @@ -1599,6 +1638,11 @@ export function ChatPage({ mobile: settings?.isMobile, }); + const autoScrollEnabled = + user?.preferences?.auto_scroll == null + ? settings?.enterpriseSettings?.auto_scroll || false + : user?.preferences?.auto_scroll!; + useScrollonStream({ chatState: currentSessionChatState, scrollableDivRef, @@ -1607,6 +1651,7 @@ export function ChatPage({ debounceNumber, waitForScrollRef, mobile: settings?.isMobile, + enableAutoScroll: autoScrollEnabled, }); // Virtualization + Scrolling related effects and functions @@ -1756,6 +1801,13 @@ export function ChatPage({ liveAssistant ); }); + + useEffect(() => { + if (!retrievalEnabled) { + setDocumentSidebarToggled(false); + } + }, [retrievalEnabled]); + const [stackTraceModalContent, setStackTraceModalContent] = useState< string | null >(null); @@ -1764,58 +1816,6 @@ export function ChatPage({ const [settingsToggled, setSettingsToggled] = useState(false); const currentPersona = alternativeAssistant || liveAssistant; - - useEffect(() => { - const handleKeyDown = (event: KeyboardEvent) => { - if (event.metaKey || event.ctrlKey) { - switch (event.key.toLowerCase()) { - case "e": - event.preventDefault(); - toggleSidebar(); - break; - } - } - }; - - window.addEventListener("keydown", handleKeyDown); - return () => { - window.removeEventListener("keydown", handleKeyDown); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [router]); - const [sharedChatSession, setSharedChatSession] = - useState(); - const [deletingChatSession, setDeletingChatSession] = - useState(); - - const showDeleteModal = (chatSession: ChatSession) => { - setDeletingChatSession(chatSession); - }; - const showShareModal = (chatSession: ChatSession) => { - setSharedChatSession(chatSession); - }; - const [documentSelection, setDocumentSelection] = useState(false); - const toggleDocumentSelectionAspects = () => { - setDocumentSelection((documentSelection) => !documentSelection); - setShowDocSidebar(false); - }; - - interface RegenerationRequest { - messageId: number; - parentMessage: Message; - } - - function createRegenerator(regenerationRequest: RegenerationRequest) { - // Returns new function that only needs `modelOverRide` to be specified when called - return async function (modelOverRide: LlmOverride) { - return await onSubmit({ - modelOverRide, - messageIdToResend: regenerationRequest.parentMessage.messageId, - regenerationRequest, - }); - }; - } - useEffect(() => { const handleSlackChatRedirect = async () => { if (!slackChatId) return; @@ -1851,18 +1851,94 @@ export function ChatPage({ handleSlackChatRedirect(); }, [searchParams, router]); + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.metaKey || event.ctrlKey) { + switch (event.key.toLowerCase()) { + case "e": + event.preventDefault(); + toggleSidebar(); + break; + } + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => { + window.removeEventListener("keydown", handleKeyDown); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [router]); + const [sharedChatSession, setSharedChatSession] = + useState(); + const [deletingChatSession, setDeletingChatSession] = + useState(); + + const showDeleteModal = (chatSession: ChatSession) => { + setDeletingChatSession(chatSession); + }; + const showShareModal = (chatSession: ChatSession) => { + setSharedChatSession(chatSession); + }; + const [documentSelection, setDocumentSelection] = useState(false); + // const toggleDocumentSelectionAspects = () => { + // setDocumentSelection((documentSelection) => !documentSelection); + // setShowDocSidebar(false); + // }; + + const toggleDocumentSidebar = () => { + if (!documentSidebarToggled) { + setFiltersToggled(false); + setDocumentSidebarToggled(true); + } else if (!filtersToggled) { + setDocumentSidebarToggled(false); + } else { + setFiltersToggled(false); + } + }; + const toggleFilters = () => { + if (!documentSidebarToggled) { + setFiltersToggled(true); + setDocumentSidebarToggled(true); + } else if (filtersToggled) { + setDocumentSidebarToggled(false); + } else { + setFiltersToggled(true); + } + }; + + interface RegenerationRequest { + messageId: number; + parentMessage: Message; + } + + function createRegenerator(regenerationRequest: RegenerationRequest) { + // Returns new function that only needs `modelOverRide` to be specified when called + return async function (modelOverRide: LlmOverride) { + return await onSubmit({ + modelOverRide, + messageIdToResend: regenerationRequest.parentMessage.messageId, + regenerationRequest, + }); + }; + } + if (noAssistants) + return ( + <> + + + + ); return ( <> - {showApiKeyModal && !shouldShowWelcomeModal ? ( + {showApiKeyModal && !shouldShowWelcomeModal && ( setShowApiKeyModal(false)} setPopup={setPopup} /> - ) : ( - noAssistants && )} {/* ChatPopup is a custom popup that displays a admin-specified message on initial user visit. @@ -1886,16 +1962,46 @@ export function ChatPage({ /> )} - {settingsToggled && ( + {(settingsToggled || userSettingsToggled) && ( setSettingsToggled(false)} + onClose={() => { + setUserSettingsToggled(false); + setSettingsToggled(false); + }} /> )} + {retrievalEnabled && documentSidebarToggled && settings?.isMobile && ( +
+ + { + setDocumentSidebarToggled(false); + }} + selectedMessage={aiMessage} + selectedDocuments={selectedDocuments} + toggleDocumentSelection={toggleDocumentSelection} + clearSelectedDocuments={clearSelectedDocuments} + selectedDocumentTokens={selectedDocumentTokens} + maxTokens={maxTokens} + initialWidth={400} + isOpen={true} + /> + +
+ )} + {deletingChatSession && (
+ {!settings?.isMobile && retrievalEnabled && ( +
+ setDocumentSidebarToggled(false)} + selectedMessage={aiMessage} + selectedDocuments={selectedDocuments} + toggleDocumentSelection={toggleDocumentSelection} + clearSelectedDocuments={clearSelectedDocuments} + selectedDocumentTokens={selectedDocumentTokens} + maxTokens={maxTokens} + initialWidth={400} + isOpen={documentSidebarToggled} + /> +
+ )} -
+
{liveAssistant && ( setUserSettingsToggled(true)} + liveAssistant={liveAssistant} + onAssistantChange={onAssistantChange} sidebarToggled={toggledSidebar} reset={() => setMessage("")} page="chat" @@ -2018,6 +2171,8 @@ export function ChatPage({ } toggleSidebar={toggleSidebar} currentChatSession={selectedChatSession} + documentSidebarToggled={documentSidebarToggled} + llmOverrideManager={llmOverrideManager} /> )} @@ -2039,7 +2194,7 @@ export function ChatPage({ duration-300 ease-in-out h-full - ${toggledSidebar ? "w-[250px]" : "w-[0px]"} + ${toggledSidebar ? "w-[200px]" : "w-[0px]"} `} >
)} @@ -2049,9 +2204,55 @@ export function ChatPage({ {...getRootProps()} >
+ {liveAssistant && onAssistantChange && ( +
+ {!settings?.isMobile && ( +
+ )} + + + {!settings?.isMobile && ( +
+ )} +
+ )} + {/* ChatBanner is a custom banner that displays a admin-specified message at the top of the chat page. Oly used in the EE version of the app. */} @@ -2059,7 +2260,7 @@ export function ChatPage({ !isFetchingChatMessages && currentSessionChatState == "input" && !loadingError && ( -
+
{ + if ( + !documentSidebarToggled || + (documentSidebarToggled && + selectedMessageForDocDisplay === + message.messageId) + ) { + toggleDocumentSidebar(); + } + setSelectedMessageForDocDisplay( + message.messageId + ); + }} docs={message.documents} currentPersona={liveAssistant} alternativeAssistant={ @@ -2268,7 +2488,6 @@ export function ChatPage({ } messageId={message.messageId} content={message.message} - // content={message.message} files={message.files} query={ messageHistory[i]?.query || undefined @@ -2454,6 +2673,15 @@ export function ChatPage({ />
)} + {messageHistory.length > 0 && ( +
+ )} {/* Some padding at the bottom so the search bar has space at the bottom to not cover the last message*/}
@@ -2477,6 +2705,15 @@ export function ChatPage({
)} { + clearSelectedDocuments(); + }} + removeFilters={() => { + filterManager.setSelectedSources([]); + filterManager.setSelectedTags([]); + filterManager.setSelectedDocumentSets([]); + setDocumentSidebarToggled(false); + }} showConfigureAPIKey={() => setShowApiKeyModal(true) } @@ -2499,6 +2736,9 @@ export function ChatPage({ llmOverrideManager={llmOverrideManager} files={currentMessageFiles} setFiles={setCurrentMessageFiles} + toggleFilters={ + retrievalEnabled ? toggleFilters : undefined + } handleFileUpload={handleImageUpload} textAreaRef={textAreaRef} chatSessionId={chatSessionIdRef.current!} @@ -2529,6 +2769,23 @@ export function ChatPage({
+ {!settings?.isMobile && ( +
+ )}
)} @@ -2537,7 +2794,11 @@ export function ChatPage({
@@ -2548,20 +2809,8 @@ export function ChatPage({
+ {/* Right Sidebar - DocumentSidebar */} - setDocumentSelection(false)} - selectedMessage={aiMessage} - selectedDocuments={selectedDocuments} - toggleDocumentSelection={toggleDocumentSelection} - clearSelectedDocuments={clearSelectedDocuments} - selectedDocumentTokens={selectedDocumentTokens} - maxTokens={maxTokens} - isLoading={isFetchingChatMessages} - isOpen={documentSelection} - /> ); } diff --git a/web/src/app/chat/documentSidebar/ChatDocumentDisplay.tsx b/web/src/app/chat/documentSidebar/ChatDocumentDisplay.tsx index 85ac429c4..5f61e4b9d 100644 --- a/web/src/app/chat/documentSidebar/ChatDocumentDisplay.tsx +++ b/web/src/app/chat/documentSidebar/ChatDocumentDisplay.tsx @@ -1,133 +1,117 @@ -import { HoverPopup } from "@/components/HoverPopup"; import { SourceIcon } from "@/components/SourceIcon"; -import { PopupSpec } from "@/components/admin/connectors/Popup"; import { DanswerDocument } from "@/lib/search/interfaces"; -import { FiInfo, FiRadio } from "react-icons/fi"; +import { FiTag } from "react-icons/fi"; import { DocumentSelector } from "./DocumentSelector"; -import { - DocumentMetadataBlock, - buildDocumentSummaryDisplay, -} from "@/components/search/DocumentDisplay"; -import { InternetSearchIcon } from "@/components/InternetSearchIcon"; +import { buildDocumentSummaryDisplay } from "@/components/search/DocumentDisplay"; +import { DocumentUpdatedAtBadge } from "@/components/search/DocumentUpdatedAtBadge"; +import { MetadataBadge } from "@/components/MetadataBadge"; +import { WebResultIcon } from "@/components/WebResultIcon"; interface DocumentDisplayProps { document: DanswerDocument; - queryEventId: number | null; - isAIPick: boolean; + modal?: boolean; isSelected: boolean; handleSelect: (documentId: string) => void; - setPopup: (popupSpec: PopupSpec | null) => void; tokenLimitReached: boolean; } +export function DocumentMetadataBlock({ + modal, + document, +}: { + modal?: boolean; + document: DanswerDocument; +}) { + const MAX_METADATA_ITEMS = 3; + const metadataEntries = Object.entries(document.metadata); + + return ( +
+ {document.updated_at && ( + + )} + + {metadataEntries.length > 0 && ( + <> +
+
+ {metadataEntries + .slice(0, MAX_METADATA_ITEMS) + .map(([key, value], index) => ( + + ))} + {metadataEntries.length > MAX_METADATA_ITEMS && ( + ... + )} +
+ + )} +
+ ); +} + export function ChatDocumentDisplay({ document, - queryEventId, - isAIPick, + modal, isSelected, handleSelect, - setPopup, tokenLimitReached, }: DocumentDisplayProps) { const isInternet = document.is_internet; - // Consider reintroducing null scored docs in the future if (document.score === null) { return null; } return ( -
-
+
+
- {isInternet ? ( - - ) : ( - - )} -

- {document.semantic_identifier || document.document_id} -

-
- {document.score !== null && ( -
- {isAIPick && ( -
- } - popupContent={ -
-
-
- -
-
The AI liked this doc!
-
-
- } - direction="bottom" - style="dark" - /> -
+
+ {document.is_internet || document.source_type === "web" ? ( + + ) : ( + )} -
- {Math.abs(document.score).toFixed(2)} +
+ {(document.semantic_identifier || document.document_id).length > + (modal ? 30 : 40) + ? `${(document.semantic_identifier || document.document_id) + .slice(0, modal ? 30 : 40) + .trim()}...` + : document.semantic_identifier || document.document_id}
- )} - - {!isInternet && ( - handleSelect(document.document_id)} - isDisabled={tokenLimitReached && !isSelected} - /> - )} -
-
-
- -
-
-

- {buildDocumentSummaryDisplay(document.match_highlights, document.blurb)} - test -

-
- {/* - // TODO: find a way to include this - {queryEventId && ( - - )} */} + +
+ {buildDocumentSummaryDisplay( + document.match_highlights, + document.blurb + )} +
+
+ {!isInternet && ( + handleSelect(document.document_id)} + isDisabled={tokenLimitReached && !isSelected} + /> + )} +
+
); diff --git a/web/src/app/chat/documentSidebar/ChatFilters.tsx b/web/src/app/chat/documentSidebar/ChatFilters.tsx new file mode 100644 index 000000000..616595abf --- /dev/null +++ b/web/src/app/chat/documentSidebar/ChatFilters.tsx @@ -0,0 +1,186 @@ +import { DanswerDocument } from "@/lib/search/interfaces"; +import { ChatDocumentDisplay } from "./ChatDocumentDisplay"; +import { usePopup } from "@/components/admin/connectors/Popup"; +import { removeDuplicateDocs } from "@/lib/documentUtils"; +import { Message } from "../interfaces"; +import { ForwardedRef, forwardRef, useEffect, useState } from "react"; +import { FilterManager } from "@/lib/hooks"; +import { CCPairBasicInfo, DocumentSet, Tag } from "@/lib/types"; +import { SourceSelector } from "../shared_chat_search/SearchFilters"; +import { XIcon } from "@/components/icons/icons"; + +interface ChatFiltersProps { + filterManager: FilterManager; + closeSidebar: () => void; + selectedMessage: Message | null; + selectedDocuments: DanswerDocument[] | null; + toggleDocumentSelection: (document: DanswerDocument) => void; + clearSelectedDocuments: () => void; + selectedDocumentTokens: number; + maxTokens: number; + initialWidth: number; + isOpen: boolean; + modal: boolean; + ccPairs: CCPairBasicInfo[]; + tags: Tag[]; + documentSets: DocumentSet[]; + showFilters: boolean; +} + +export const ChatFilters = forwardRef( + ( + { + closeSidebar, + modal, + selectedMessage, + selectedDocuments, + filterManager, + toggleDocumentSelection, + clearSelectedDocuments, + selectedDocumentTokens, + maxTokens, + initialWidth, + isOpen, + ccPairs, + tags, + documentSets, + showFilters, + }, + ref: ForwardedRef + ) => { + const { popup, setPopup } = usePopup(); + const [delayedSelectedDocumentCount, setDelayedSelectedDocumentCount] = + useState(0); + + useEffect(() => { + const timer = setTimeout( + () => { + setDelayedSelectedDocumentCount(selectedDocuments?.length || 0); + }, + selectedDocuments?.length == 0 ? 1000 : 0 + ); + + return () => clearTimeout(timer); + }, [selectedDocuments]); + + const selectedDocumentIds = + selectedDocuments?.map((document) => document.document_id) || []; + + const currentDocuments = selectedMessage?.documents || null; + const dedupedDocuments = removeDuplicateDocs(currentDocuments || []); + + const tokenLimitReached = selectedDocumentTokens > maxTokens - 75; + + const hasSelectedDocuments = selectedDocumentIds.length > 0; + + return ( +
{ + if (e.target === e.currentTarget) { + closeSidebar(); + } + }} + > +
+
+ {popup} +
+

+ {showFilters ? "Filters" : "Sources"} +

+ +
+
+
+ {showFilters ? ( + ccPair.source)} + availableTags={tags} + /> + ) : ( + <> + {dedupedDocuments.length > 0 ? ( + dedupedDocuments.map((document, ind) => ( +
+ { + toggleDocumentSelection( + dedupedDocuments.find( + (doc) => doc.document_id === documentId + )! + ); + }} + tokenLimitReached={tokenLimitReached} + /> +
+ )) + ) : ( +
+ )} + + )} +
+
+ {!showFilters && ( +
+ +
+ )} +
+
+ ); + } +); + +ChatFilters.displayName = "ChatFilters"; diff --git a/web/src/app/chat/documentSidebar/DocumentSidebar.tsx b/web/src/app/chat/documentSidebar/DocumentSidebar.tsx deleted file mode 100644 index 021c23981..000000000 --- a/web/src/app/chat/documentSidebar/DocumentSidebar.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import { DanswerDocument } from "@/lib/search/interfaces"; -import Text from "@/components/ui/text"; -import { ChatDocumentDisplay } from "./ChatDocumentDisplay"; -import { usePopup } from "@/components/admin/connectors/Popup"; -import { removeDuplicateDocs } from "@/lib/documentUtils"; -import { Message } from "../interfaces"; -import { ForwardedRef, forwardRef } from "react"; -import { Separator } from "@/components/ui/separator"; - -interface DocumentSidebarProps { - closeSidebar: () => void; - selectedMessage: Message | null; - selectedDocuments: DanswerDocument[] | null; - toggleDocumentSelection: (document: DanswerDocument) => void; - clearSelectedDocuments: () => void; - selectedDocumentTokens: number; - maxTokens: number; - isLoading: boolean; - initialWidth: number; - isOpen: boolean; -} - -export const DocumentSidebar = forwardRef( - ( - { - closeSidebar, - selectedMessage, - selectedDocuments, - toggleDocumentSelection, - clearSelectedDocuments, - selectedDocumentTokens, - maxTokens, - isLoading, - initialWidth, - isOpen, - }, - ref: ForwardedRef - ) => { - const { popup, setPopup } = usePopup(); - - const selectedDocumentIds = - selectedDocuments?.map((document) => document.document_id) || []; - - const currentDocuments = selectedMessage?.documents || null; - const dedupedDocuments = removeDuplicateDocs(currentDocuments || []); - - // NOTE: do not allow selection if less than 75 tokens are left - // this is to prevent the case where they are able to select the doc - // but it basically is unused since it's truncated right at the very - // start of the document (since title + metadata + misc overhead) takes up - // space - const tokenLimitReached = selectedDocumentTokens > maxTokens - 75; - - return ( -
{ - if (e.target === e.currentTarget) { - closeSidebar(); - } - }} - > -
-
- {popup} -
- {dedupedDocuments.length} Document - {dedupedDocuments.length > 1 ? "s" : ""} -

- Select to add to continuous context - - Learn more - -

-
- - - - {currentDocuments ? ( -
- {dedupedDocuments.length > 0 ? ( - dedupedDocuments.map((document, ind) => ( -
- { - toggleDocumentSelection( - dedupedDocuments.find( - (document) => document.document_id === documentId - )! - ); - }} - tokenLimitReached={tokenLimitReached} - /> -
- )) - ) : ( -
- No documents found for the query. -
- )} -
- ) : ( - !isLoading && ( -
- - When you run ask a question, the retrieved documents will - show up here! - -
- ) - )} -
- -
-
- - - -
-
-
- ); - } -); - -DocumentSidebar.displayName = "DocumentSidebar"; diff --git a/web/src/app/chat/input/ChatInputBar.tsx b/web/src/app/chat/input/ChatInputBar.tsx index 9dd3d5274..5d786a178 100644 --- a/web/src/app/chat/input/ChatInputBar.tsx +++ b/web/src/app/chat/input/ChatInputBar.tsx @@ -1,13 +1,9 @@ import React, { useContext, useEffect, useRef, useState } from "react"; -import { FiPlusCircle, FiPlus, FiInfo, FiX } from "react-icons/fi"; +import { FiPlusCircle, FiPlus, FiInfo, FiX, FiSearch } from "react-icons/fi"; import { ChatInputOption } from "./ChatInputOption"; import { Persona } from "@/app/admin/assistants/interfaces"; import { InputPrompt } from "@/app/admin/prompt-library/interfaces"; -import { - FilterManager, - getDisplayNameForModel, - LlmOverrideManager, -} from "@/lib/hooks"; +import { FilterManager, LlmOverrideManager } from "@/lib/hooks"; import { SelectedFilterDisplay } from "./SelectedFilterDisplay"; import { useChatContext } from "@/components/context/ChatContext"; import { getFinalLLM } from "@/lib/llm/utils"; @@ -18,15 +14,10 @@ import { } from "../files/InputBarPreview"; import { AssistantsIconSkeleton, - CpuIconSkeleton, FileIcon, SendIcon, StopGeneratingIcon, } from "@/components/icons/icons"; -import { IconType } from "react-icons"; -import Popup from "../../../components/popup/Popup"; -import { LlmTab } from "../modal/configuration/LlmTab"; -import { AssistantsTab } from "../modal/configuration/AssistantsTab"; import { DanswerDocument } from "@/lib/search/interfaces"; import { AssistantIcon } from "@/components/assistants/AssistantIcon"; import { @@ -40,10 +31,18 @@ import { SettingsContext } from "@/components/settings/SettingsProvider"; import { ChatState } from "../types"; import UnconfiguredProviderText from "@/components/chat_search/UnconfiguredProviderText"; import { useAssistants } from "@/components/context/AssistantsContext"; +import AnimatedToggle from "@/components/search/SearchBar"; +import { Popup } from "@/components/admin/connectors/Popup"; +import { AssistantsTab } from "../modal/configuration/AssistantsTab"; +import { IconType } from "react-icons"; +import { LlmTab } from "../modal/configuration/LlmTab"; +import { XIcon } from "lucide-react"; const MAX_INPUT_HEIGHT = 200; export function ChatInputBar({ + removeFilters, + removeDocs, openModelSettings, showDocs, showConfigureAPIKey, @@ -68,7 +67,10 @@ export function ChatInputBar({ alternativeAssistant, chatSessionId, inputPrompts, + toggleFilters, }: { + removeFilters: () => void; + removeDocs: () => void; showConfigureAPIKey: () => void; openModelSettings: () => void; chatState: ChatState; @@ -90,6 +92,7 @@ export function ChatInputBar({ handleFileUpload: (files: File[]) => void; textAreaRef: React.RefObject; chatSessionId?: string; + toggleFilters?: () => void; }) { useEffect(() => { const textarea = textAreaRef.current; @@ -370,9 +373,9 @@ export function ChatInputBar({
)} -
+ {/*
-
+
*/} @@ -429,16 +432,21 @@ export function ChatInputBar({ )} {(selectedDocuments.length > 0 || files.length > 0) && (
-
+
{selectedDocuments.length > 0 && ( )} {files.map((file) => ( @@ -529,72 +537,6 @@ export function ChatInputBar({ suppressContentEditableWarning={true} />
- ( - { - setSelectedAssistant(assistant); - close(); - }} - /> - )} - flexPriority="shrink" - position="top" - mobilePosition="top-right" - > - - - ( - - )} - position="top" - > - - - + {toggleFilters && ( + + )}
diff --git a/web/src/app/chat/lib.tsx b/web/src/app/chat/lib.tsx index 005297764..b5264ba1c 100644 --- a/web/src/app/chat/lib.tsx +++ b/web/src/app/chat/lib.tsx @@ -2,6 +2,7 @@ import { AnswerPiecePacket, DanswerDocument, Filters, + FinalContextDocs, StreamStopInfo, } from "@/lib/search/interfaces"; import { handleSSEStream } from "@/lib/search/streamingUtils"; @@ -102,6 +103,7 @@ export type PacketType = | ToolCallMetadata | BackendMessage | AnswerPiecePacket + | FinalContextDocs | DocumentsResponse | FileChatDisplay | StreamingError @@ -147,7 +149,6 @@ export async function* sendMessage({ }): AsyncGenerator { const documentsAreSelected = selectedDocumentIds && selectedDocumentIds.length > 0; - const body = JSON.stringify({ alternate_assistant_id: alternateAssistantId, chat_session_id: chatSessionId, @@ -639,6 +640,7 @@ export async function useScrollonStream({ endDivRef, debounceNumber, mobile, + enableAutoScroll, }: { chatState: ChatState; scrollableDivRef: RefObject; @@ -647,6 +649,7 @@ export async function useScrollonStream({ endDivRef: RefObject; debounceNumber: number; mobile?: boolean; + enableAutoScroll?: boolean; }) { const mobileDistance = 900; // distance that should "engage" the scroll const desktopDistance = 500; // distance that should "engage" the scroll @@ -659,6 +662,10 @@ export async function useScrollonStream({ const previousScroll = useRef(0); useEffect(() => { + if (!enableAutoScroll) { + return; + } + if (chatState != "input" && scrollableDivRef && scrollableDivRef.current) { const newHeight: number = scrollableDivRef.current?.scrollTop!; const heightDifference = newHeight - previousScroll.current; @@ -716,7 +723,7 @@ export async function useScrollonStream({ // scroll on end of stream if within distance useEffect(() => { - if (scrollableDivRef?.current && chatState == "input") { + if (scrollableDivRef?.current && chatState == "input" && enableAutoScroll) { if (scrollDist.current < distance - 50) { scrollableDivRef?.current?.scrollBy({ left: 0, diff --git a/web/src/app/chat/message/MemoizedTextComponents.tsx b/web/src/app/chat/message/MemoizedTextComponents.tsx index 9ab0e28e3..7c8144e8c 100644 --- a/web/src/app/chat/message/MemoizedTextComponents.tsx +++ b/web/src/app/chat/message/MemoizedTextComponents.tsx @@ -1,8 +1,50 @@ import { Citation } from "@/components/search/results/Citation"; +import { WebResultIcon } from "@/components/WebResultIcon"; +import { LoadedDanswerDocument } from "@/lib/search/interfaces"; +import { getSourceMetadata } from "@/lib/sources"; +import { ValidSources } from "@/lib/types"; import React, { memo } from "react"; +import isEqual from "lodash/isEqual"; + +export const MemoizedAnchor = memo(({ docs, children }: any) => { + console.log(children); + const value = children?.toString(); + if (value?.startsWith("[") && value?.endsWith("]")) { + const match = value.match(/\[(\d+)\]/); + if (match) { + const index = parseInt(match[1], 10) - 1; + const associatedDoc = docs && docs[index]; + + const url = associatedDoc?.link + ? new URL(associatedDoc.link).origin + "/favicon.ico" + : ""; + + const getIcon = (sourceType: ValidSources, link: string) => { + return getSourceMetadata(sourceType).icon({ size: 18 }); + }; + + const icon = + associatedDoc?.source_type === "web" ? ( + + ) : ( + getIcon( + associatedDoc?.source_type || "web", + associatedDoc?.link || "" + ) + ); + + return ( + + {children} + + ); + } + } + return {children}; +}); export const MemoizedLink = memo((props: any) => { - const { node, ...rest } = props; + const { node, document, ...rest } = props; const value = rest.children; if (value?.toString().startsWith("*")) { @@ -10,7 +52,16 @@ export const MemoizedLink = memo((props: any) => {
); } else if (value?.toString().startsWith("[")) { - return {rest.children}; + return ( + + {rest.children} + + ); } else { return ( { } }); -export const MemoizedParagraph = memo(({ ...props }: any) => { - return

; -}); +export const MemoizedParagraph = memo( + function MemoizedParagraph({ children }: any) { + return

{children}

; + }, + (prevProps, nextProps) => { + const areEqual = isEqual(prevProps.children, nextProps.children); + return areEqual; + } +); +MemoizedAnchor.displayName = "MemoizedAnchor"; MemoizedLink.displayName = "MemoizedLink"; MemoizedParagraph.displayName = "MemoizedParagraph"; diff --git a/web/src/app/chat/message/Messages.tsx b/web/src/app/chat/message/Messages.tsx index cc4f9c9ca..0aa9ba826 100644 --- a/web/src/app/chat/message/Messages.tsx +++ b/web/src/app/chat/message/Messages.tsx @@ -8,14 +8,22 @@ import { FiGlobe, } from "react-icons/fi"; import { FeedbackType } from "../types"; -import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; +import React, { + memo, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import ReactMarkdown from "react-markdown"; import { DanswerDocument, FilteredDanswerDocument, } from "@/lib/search/interfaces"; import { SearchSummary } from "./SearchSummary"; -import { SourceIcon } from "@/components/SourceIcon"; + import { SkippedSearch } from "./SkippedSearch"; import remarkGfm from "remark-gfm"; import { CopyButton } from "@/components/CopyButton"; @@ -36,8 +44,6 @@ import "prismjs/themes/prism-tomorrow.css"; import "./custom-code-styles.css"; import { Persona } from "@/app/admin/assistants/interfaces"; import { AssistantIcon } from "@/components/assistants/AssistantIcon"; -import { Citation } from "@/components/search/results/Citation"; -import { DocumentMetadataBlock } from "@/components/search/DocumentDisplay"; import { LikeFeedback, DislikeFeedback } from "@/components/icons/icons"; import { @@ -52,16 +58,18 @@ import { TooltipTrigger, } from "@/components/ui/tooltip"; import { useMouseTracking } from "./hooks"; -import { InternetSearchIcon } from "@/components/InternetSearchIcon"; import { SettingsContext } from "@/components/settings/SettingsProvider"; import GeneratingImageDisplay from "../tools/GeneratingImageDisplay"; import RegenerateOption from "../RegenerateOption"; import { LlmOverride } from "@/lib/hooks"; import { ContinueGenerating } from "./ContinueMessage"; -import { MemoizedLink, MemoizedParagraph } from "./MemoizedTextComponents"; +import { MemoizedAnchor, MemoizedParagraph } from "./MemoizedTextComponents"; import { extractCodeText } from "./codeUtils"; import ToolResult from "../../../components/tools/ToolResult"; import CsvContent from "../../../components/tools/CSVContent"; +import SourceCard, { + SeeMoreBlock, +} from "@/components/chat_search/sources/SourceCard"; const TOOLS_WITH_CUSTOM_HANDLING = [ SEARCH_TOOL_NAME, @@ -155,6 +163,7 @@ function FileDisplay({ export const AIMessage = ({ regenerate, overriddenModel, + selectedMessageForDocDisplay, continueGenerating, shared, isActive, @@ -162,6 +171,7 @@ export const AIMessage = ({ alternativeAssistant, docs, messageId, + documentSelectionToggled, content, files, selectedDocuments, @@ -178,7 +188,10 @@ export const AIMessage = ({ currentPersona, otherMessagesCanSwitchTo, onMessageSelection, + index, }: { + index?: number; + selectedMessageForDocDisplay?: number | null; shared?: boolean; isActive?: boolean; continueGenerating?: () => void; @@ -191,6 +204,7 @@ export const AIMessage = ({ currentPersona: Persona; messageId: number | null; content: string | JSX.Element; + documentSelectionToggled?: boolean; files?: FileDescriptor[]; query?: string; citedDocuments?: [string, DanswerDocument][] | null; @@ -287,18 +301,31 @@ export const AIMessage = ({ }); } + const paragraphCallback = useCallback( + (props: any) => {props.children}, + [] + ); + + const anchorCallback = useCallback( + (props: any) => ( + {props.children} + ), + [docs] + ); + const currentMessageInd = messageId ? otherMessagesCanSwitchTo?.indexOf(messageId) : undefined; + const uniqueSources: ValidSources[] = Array.from( new Set((docs || []).map((doc) => doc.source_type)) ).slice(0, 3); const markdownComponents = useMemo( () => ({ - a: MemoizedLink, - p: MemoizedParagraph, - code: ({ node, className, children, ...props }: any) => { + a: anchorCallback, + p: paragraphCallback, + code: ({ node, className, children }: any) => { const codeText = extractCodeText( node, finalContent as string, @@ -312,7 +339,7 @@ export const AIMessage = ({ ); }, }), - [finalContent] + [anchorCallback, paragraphCallback, finalContent] ); const renderedMarkdown = useMemo(() => { @@ -333,12 +360,11 @@ export const AIMessage = ({ onMessageSelection && otherMessagesCanSwitchTo && otherMessagesCanSwitchTo.length > 1; - return (
)} + {docs && docs.length > 0 && ( +
+
+
+ {!settings?.isMobile && + docs.length > 0 && + docs + .slice(0, 2) + .map((doc, ind) => ( + + ))} + +
+
+
+ )} + {content || files ? ( <> @@ -438,81 +490,6 @@ export const AIMessage = ({ ) : isComplete ? null : ( <> )} - {isComplete && docs && docs.length > 0 && ( -
-
-
- {!settings?.isMobile && - filteredDocs.length > 0 && - filteredDocs.slice(0, 2).map((doc, ind) => ( - - ))} -
{ - if (messageId) { - onMessageSelection?.(messageId); - } - toggleDocumentSelection?.(); - }} - key={-1} - className="cursor-pointer w-[200px] rounded-lg flex-none transition-all duration-500 hover:bg-background-125 bg-text-100 px-4 py-2 border-b" - > -
-

See context

-
- {uniqueSources.map((sourceType, ind) => { - return ( -
- -
- ); - })} -
-
-
- See more -
-
-
-
-
- )}
{handleFeedback && diff --git a/web/src/app/chat/message/SearchSummary.tsx b/web/src/app/chat/message/SearchSummary.tsx index f86212fd2..7349ec6ca 100644 --- a/web/src/app/chat/message/SearchSummary.tsx +++ b/web/src/app/chat/message/SearchSummary.tsx @@ -41,6 +41,7 @@ export function ShowHideDocsButton({ } export function SearchSummary({ + index, query, hasDocs, finished, @@ -48,6 +49,7 @@ export function SearchSummary({ handleShowRetrieved, handleSearchQueryEdit, }: { + index: number; finished: boolean; query: string; hasDocs: boolean; @@ -98,7 +100,14 @@ export function SearchSummary({ !text-sm !line-clamp-1 !break-all px-0.5`} ref={searchingForRef} > - {finished ? "Searched" : "Searching"} for: {finalQuery} + {finished ? "Searched" : "Searching"} for:{" "} + + {index === 1 + ? finalQuery.length > 50 + ? `${finalQuery.slice(0, 50)}...` + : finalQuery + : finalQuery} +
); diff --git a/web/src/app/chat/modal/FeedbackModal.tsx b/web/src/app/chat/modal/FeedbackModal.tsx index 886a761ac..e050dcc62 100644 --- a/web/src/app/chat/modal/FeedbackModal.tsx +++ b/web/src/app/chat/modal/FeedbackModal.tsx @@ -53,7 +53,7 @@ export const FeedbackModal = ({ : predefinedNegativeFeedbackOptions; return ( - + <>

diff --git a/web/src/app/chat/modal/SetDefaultModelModal.tsx b/web/src/app/chat/modal/SetDefaultModelModal.tsx index 27696c469..22e7f60ad 100644 --- a/web/src/app/chat/modal/SetDefaultModelModal.tsx +++ b/web/src/app/chat/modal/SetDefaultModelModal.tsx @@ -1,4 +1,4 @@ -import { Dispatch, SetStateAction, useEffect, useRef } from "react"; +import { Dispatch, SetStateAction, useContext, useEffect, useRef } from "react"; import { Modal } from "@/components/Modal"; import Text from "@/components/ui/text"; import { getDisplayNameForModel, LlmOverride } from "@/lib/hooks"; @@ -9,6 +9,10 @@ import { setUserDefaultModel } from "@/lib/users/UserSettings"; import { useRouter } from "next/navigation"; import { PopupSpec } from "@/components/admin/connectors/Popup"; import { useUser } from "@/components/user/UserProvider"; +import { Separator } from "@/components/ui/separator"; +import { Switch } from "@/components/ui/switch"; +import { Label } from "@/components/admin/connectors/Field"; +import { SettingsContext } from "@/components/settings/SettingsProvider"; export function SetDefaultModelModal({ setPopup, @@ -23,7 +27,7 @@ export function SetDefaultModelModal({ onClose: () => void; defaultModel: string | null; }) { - const { refreshUser } = useUser(); + const { refreshUser, user, updateUserAutoScroll } = useUser(); const containerRef = useRef(null); const messageRef = useRef(null); @@ -121,16 +125,41 @@ export function SetDefaultModelModal({ const defaultProvider = llmProviders.find( (llmProvider) => llmProvider.is_default_provider ); + const settings = useContext(SettingsContext); + const autoScroll = settings?.enterpriseSettings?.auto_scroll; + + const checked = + user?.preferences?.auto_scroll === null + ? autoScroll + : user?.preferences?.auto_scroll; return ( <>

- Set Default Model + User settings

+
+
+ { + updateUserAutoScroll(checked); + }} + /> + +
+
+ + + +

+ Default model for assistants +

+ Choose a Large Language Model (LLM) to serve as the default for assistants that don't have a default model assigned. diff --git a/web/src/app/chat/page.tsx b/web/src/app/chat/page.tsx index 7894ce651..274f362d4 100644 --- a/web/src/app/chat/page.tsx +++ b/web/src/app/chat/page.tsx @@ -32,6 +32,7 @@ export default async function Page(props: { defaultAssistantId, shouldShowWelcomeModal, userInputPrompts, + ccPairs, } = data; return ( @@ -44,6 +45,9 @@ export default async function Page(props: { value={{ chatSessions, availableSources, + ccPairs, + documentSets, + tags, availableDocumentSets: documentSets, availableTags: tags, llmProviders, diff --git a/web/src/app/chat/sessionSidebar/HistorySidebar.tsx b/web/src/app/chat/sessionSidebar/HistorySidebar.tsx index d2ebf1c07..88002ee00 100644 --- a/web/src/app/chat/sessionSidebar/HistorySidebar.tsx +++ b/web/src/app/chat/sessionSidebar/HistorySidebar.tsx @@ -113,7 +113,7 @@ export const HistorySidebar = forwardRef( {page == "chat" && (
( +
{children}
+); + +export interface SourceSelectorProps { + timeRange: DateRangePickerValue | null; + setTimeRange: React.Dispatch< + React.SetStateAction + >; + showDocSidebar?: boolean; + selectedSources: SourceMetadata[]; + setSelectedSources: React.Dispatch>; + selectedDocumentSets: string[]; + setSelectedDocumentSets: React.Dispatch>; + selectedTags: Tag[]; + setSelectedTags: React.Dispatch>; + availableDocumentSets: DocumentSet[]; + existingSources: ValidSources[]; + availableTags: Tag[]; + toggleFilters: () => void; + filtersUntoggled: boolean; + tagsOnLeft: boolean; +} + +export function SourceSelector({ + timeRange, + setTimeRange, + selectedSources, + setSelectedSources, + selectedDocumentSets, + setSelectedDocumentSets, + selectedTags, + setSelectedTags, + availableDocumentSets, + existingSources, + availableTags, + showDocSidebar, + toggleFilters, + filtersUntoggled, + tagsOnLeft, +}: SourceSelectorProps) { + const handleSelect = (source: SourceMetadata) => { + setSelectedSources((prev: SourceMetadata[]) => { + if ( + prev.map((source) => source.internalName).includes(source.internalName) + ) { + return prev.filter((s) => s.internalName !== source.internalName); + } else { + return [...prev, source]; + } + }); + }; + + const handleDocumentSetSelect = (documentSetName: string) => { + setSelectedDocumentSets((prev: string[]) => { + if (prev.includes(documentSetName)) { + return prev.filter((s) => s !== documentSetName); + } else { + return [...prev, documentSetName]; + } + }); + }; + + let allSourcesSelected = selectedSources.length > 0; + + const toggleAllSources = () => { + if (allSourcesSelected) { + setSelectedSources([]); + } else { + const allSources = listSourceMetadata().filter((source) => + existingSources.includes(source.internalName) + ); + setSelectedSources(allSources); + } + }; + + return ( +
+ + {!filtersUntoggled && ( + <> + + + +
+
+ Time Range + {true && ( + + )} +
+

+ {getTimeAgoString(timeRange?.from!) || "Select a time range"} +

+
+
+ + { + const initialDate = daterange?.from || new Date(); + const endDate = daterange?.to || new Date(); + setTimeRange({ + from: initialDate, + to: endDate, + selectValue: timeRange?.selectValue || "", + }); + }} + className="rounded-md " + /> + +
+ + {availableTags.length > 0 && ( + <> +
+ Tags +
+ + + )} + + {existingSources.length > 0 && ( +
+
+
+

Sources

+ +
+
+
+ {listSourceMetadata() + .filter((source) => + existingSources.includes(source.internalName) + ) + .map((source) => ( +
source.internalName) + .includes(source.internalName) + ? "bg-hover" + : "hover:bg-hover-light") + } + onClick={() => handleSelect(source)} + > + + + {source.displayName} + +
+ ))} +
+
+ )} + + {availableDocumentSets.length > 0 && ( + <> +
+ Knowledge Sets +
+
+ {availableDocumentSets.map((documentSet) => ( +
+
handleDocumentSetSelect(documentSet.name)} + > + + +
+ } + popupContent={ +
+
Description
+
+ {documentSet.description} +
+
+ } + classNameModifications="-ml-2" + /> + {documentSet.name} +
+
+ ))} +
+ + )} + + )} +
+ ); +} + +export function SelectedBubble({ + children, + onClick, +}: { + children: string | JSX.Element; + onClick: () => void; +}) { + return ( +
+ {children} + +
+ ); +} + +export function HorizontalFilters({ + timeRange, + setTimeRange, + selectedSources, + setSelectedSources, + selectedDocumentSets, + setSelectedDocumentSets, + availableDocumentSets, + existingSources, +}: SourceSelectorProps) { + const handleSourceSelect = (source: SourceMetadata) => { + setSelectedSources((prev: SourceMetadata[]) => { + const prevSourceNames = prev.map((source) => source.internalName); + if (prevSourceNames.includes(source.internalName)) { + return prev.filter((s) => s.internalName !== source.internalName); + } else { + return [...prev, source]; + } + }); + }; + + const handleDocumentSetSelect = (documentSetName: string) => { + setSelectedDocumentSets((prev: string[]) => { + if (prev.includes(documentSetName)) { + return prev.filter((s) => s !== documentSetName); + } else { + return [...prev, documentSetName]; + } + }); + }; + + const allSources = listSourceMetadata(); + const availableSources = allSources.filter((source) => + existingSources.includes(source.internalName) + ); + + return ( +
+
+
+ +
+ + { + return { + key: source.displayName, + display: ( + <> + + {source.displayName} + + ), + }; + })} + selected={selectedSources.map((source) => source.displayName)} + handleSelect={(option) => + handleSourceSelect( + allSources.find((source) => source.displayName === option.key)! + ) + } + icon={ +
+ +
+ } + defaultDisplay="All Sources" + /> + + { + return { + key: documentSet.name, + display: ( + <> +
+ +
+ {documentSet.name} + + ), + }; + })} + selected={selectedDocumentSets} + handleSelect={(option) => handleDocumentSetSelect(option.key)} + icon={ +
+ +
+ } + defaultDisplay="All Document Sets" + /> +
+ +
+
+ {timeRange && timeRange.selectValue && ( + setTimeRange(null)}> +
{timeRange.selectValue}
+
+ )} + {existingSources.length > 0 && + selectedSources.map((source) => ( + handleSourceSelect(source)} + > + <> + + {source.displayName} + + + ))} + {selectedDocumentSets.length > 0 && + selectedDocumentSets.map((documentSetName) => ( + handleDocumentSetSelect(documentSetName)} + > + <> +
+ +
+ {documentSetName} + +
+ ))} +
+
+
+ ); +} + +export function HorizontalSourceSelector({ + timeRange, + setTimeRange, + selectedSources, + setSelectedSources, + selectedDocumentSets, + setSelectedDocumentSets, + selectedTags, + setSelectedTags, + availableDocumentSets, + existingSources, + availableTags, +}: SourceSelectorProps) { + const handleSourceSelect = (source: SourceMetadata) => { + setSelectedSources((prev: SourceMetadata[]) => { + if (prev.map((s) => s.internalName).includes(source.internalName)) { + return prev.filter((s) => s.internalName !== source.internalName); + } else { + return [...prev, source]; + } + }); + }; + + const handleDocumentSetSelect = (documentSetName: string) => { + setSelectedDocumentSets((prev: string[]) => { + if (prev.includes(documentSetName)) { + return prev.filter((s) => s !== documentSetName); + } else { + return [...prev, documentSetName]; + } + }); + }; + + const handleTagSelect = (tag: Tag) => { + setSelectedTags((prev: Tag[]) => { + if ( + prev.some( + (t) => t.tag_key === tag.tag_key && t.tag_value === tag.tag_value + ) + ) { + return prev.filter( + (t) => !(t.tag_key === tag.tag_key && t.tag_value === tag.tag_value) + ); + } else { + return [...prev, tag]; + } + }); + }; + + const resetSources = () => { + setSelectedSources([]); + }; + const resetDocuments = () => { + setSelectedDocumentSets([]); + }; + + const resetTags = () => { + setSelectedTags([]); + }; + + return ( +
+ + +
+ + + {timeRange?.from ? getTimeAgoString(timeRange.from) : "Since"} +
+
+ + { + const initialDate = daterange?.from || new Date(); + const endDate = daterange?.to || new Date(); + setTimeRange({ + from: initialDate, + to: endDate, + selectValue: timeRange?.selectValue || "", + }); + }} + className="rounded-md" + /> + +
+ + {existingSources.length > 0 && ( + existingSources.includes(source.internalName)) + .map((source) => ({ + key: source.internalName, + display: ( + <> + + {source.displayName} + + ), + }))} + selected={selectedSources.map((source) => source.internalName)} + handleSelect={(option) => + handleSourceSelect( + listSourceMetadata().find((s) => s.internalName === option.key)! + ) + } + icon={} + defaultDisplay="Sources" + dropdownColor="bg-background-search-filter-dropdown" + width="w-fit ellipsis truncate" + resetValues={resetSources} + dropdownWidth="w-40" + optionClassName="truncate w-full break-all ellipsis" + /> + )} + + {availableDocumentSets.length > 0 && ( + ({ + key: documentSet.name, + display: <>{documentSet.name}, + }))} + selected={selectedDocumentSets} + handleSelect={(option) => handleDocumentSetSelect(option.key)} + icon={} + defaultDisplay="Sets" + resetValues={resetDocuments} + width="w-fit max-w-24 text-ellipsis truncate" + dropdownColor="bg-background-search-filter-dropdown" + dropdownWidth="max-w-36 w-fit" + optionClassName="truncate w-full break-all" + /> + )} + + {availableTags.length > 0 && ( + ({ + key: `${tag.tag_key}=${tag.tag_value}`, + display: ( + + {tag.tag_key} + = + {tag.tag_value} + + ), + }))} + selected={selectedTags.map( + (tag) => `${tag.tag_key}=${tag.tag_value}` + )} + handleSelect={(option) => { + const [tag_key, tag_value] = option.key.split("="); + const selectedTag = availableTags.find( + (tag) => tag.tag_key === tag_key && tag.tag_value === tag_value + ); + if (selectedTag) { + handleTagSelect(selectedTag); + } + }} + icon={} + defaultDisplay="Tags" + resetValues={resetTags} + dropdownColor="bg-background-search-filter-dropdown" + width="w-fit max-w-24 ellipsis truncate" + dropdownWidth="max-w-80 w-fit" + optionClassName="truncate w-full break-all ellipsis" + /> + )} +
+ ); +} diff --git a/web/src/app/chat/shared_chat_search/FixedLogo.tsx b/web/src/app/chat/shared_chat_search/FixedLogo.tsx index 5bf48c277..c7e7a7d2a 100644 --- a/web/src/app/chat/shared_chat_search/FixedLogo.tsx +++ b/web/src/app/chat/shared_chat_search/FixedLogo.tsx @@ -21,9 +21,7 @@ export default function FixedLogo({ return ( <>
@@ -49,7 +47,7 @@ export default function FixedLogo({
- + {/* */}
); diff --git a/web/src/app/chat/shared_chat_search/FunctionalWrapper.tsx b/web/src/app/chat/shared_chat_search/FunctionalWrapper.tsx index e8c377dc5..8a58c6391 100644 --- a/web/src/app/chat/shared_chat_search/FunctionalWrapper.tsx +++ b/web/src/app/chat/shared_chat_search/FunctionalWrapper.tsx @@ -1,90 +1,7 @@ "use client"; -import React, { ReactNode, useContext, useEffect, useState } from "react"; -import { usePathname, useRouter } from "next/navigation"; -import { ChatIcon, SearchIcon } from "@/components/icons/icons"; -import { SettingsContext } from "@/components/settings/SettingsProvider"; -import KeyboardSymbol from "@/lib/browserUtilities"; - -const ToggleSwitch = () => { - const commandSymbol = KeyboardSymbol(); - const pathname = usePathname(); - const router = useRouter(); - const settings = useContext(SettingsContext); - - const [activeTab, setActiveTab] = useState(() => { - return pathname == "/search" ? "search" : "chat"; - }); - - const [isInitialLoad, setIsInitialLoad] = useState(true); - - useEffect(() => { - const newTab = pathname === "/search" ? "search" : "chat"; - setActiveTab(newTab); - localStorage.setItem("activeTab", newTab); - setIsInitialLoad(false); - }, [pathname]); - - const handleTabChange = (tab: string) => { - setActiveTab(tab); - localStorage.setItem("activeTab", tab); - if (settings?.isMobile && window) { - window.location.href = tab; - } else { - router.push(tab === "search" ? "/search" : "/chat"); - } - }; - - return ( -
-
- - -
- ); -}; +import React, { ReactNode, useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; export default function FunctionalWrapper({ initiallyToggled, @@ -128,12 +45,6 @@ export default function FunctionalWrapper({ window.removeEventListener("keydown", handleKeyDown); }; }, [router]); - const combinedSettings = useContext(SettingsContext); - const settings = combinedSettings?.settings; - const chatBannerPresent = - combinedSettings?.enterpriseSettings?.custom_header_content; - const twoLines = - combinedSettings?.enterpriseSettings?.two_lines_for_chat_header; const [toggledSidebar, setToggledSidebar] = useState(initiallyToggled); @@ -145,24 +56,7 @@ export default function FunctionalWrapper({ return ( <> - {(!settings || - (settings.search_page_enabled && settings.chat_page_enabled)) && ( -
-
-
- -
-
- )} - + {" "}
{content(toggledSidebar, toggle)}
diff --git a/web/src/app/chat/shared_chat_search/SearchFilters.tsx b/web/src/app/chat/shared_chat_search/SearchFilters.tsx new file mode 100644 index 000000000..46ceda9a7 --- /dev/null +++ b/web/src/app/chat/shared_chat_search/SearchFilters.tsx @@ -0,0 +1,294 @@ +import { DocumentSet, Tag, ValidSources } from "@/lib/types"; +import { SourceMetadata } from "@/lib/search/interfaces"; +import { InfoIcon, defaultTailwindCSS } from "@/components/icons/icons"; +import { HoverPopup } from "@/components/HoverPopup"; +import { DateRangePickerValue } from "@/app/ee/admin/performance/DateRangeSelector"; +import { SourceIcon } from "@/components/SourceIcon"; +import { Checkbox } from "@/components/ui/checkbox"; +import { TagFilter } from "@/components/search/filtering/TagFilter"; +import { CardContent } from "@/components/ui/card"; +import { useEffect } from "react"; +import { useState } from "react"; +import { listSourceMetadata } from "@/lib/sources"; +import { Calendar } from "@/components/ui/calendar"; +import { getDateRangeString } from "@/lib/dateUtils"; +import { Button } from "@/components/ui/button"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { ToolTipDetails } from "@/components/admin/connectors/Field"; + +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { TooltipProvider } from "@radix-ui/react-tooltip"; + +const SectionTitle = ({ + children, + modal, +}: { + children: string; + modal?: boolean; +}) => ( +
+

{children}

+
+); + +export interface SourceSelectorProps { + timeRange: DateRangePickerValue | null; + setTimeRange: React.Dispatch< + React.SetStateAction + >; + showDocSidebar?: boolean; + selectedSources: SourceMetadata[]; + setSelectedSources: React.Dispatch>; + selectedDocumentSets: string[]; + setSelectedDocumentSets: React.Dispatch>; + selectedTags: Tag[]; + setSelectedTags: React.Dispatch>; + availableDocumentSets: DocumentSet[]; + existingSources: ValidSources[]; + availableTags: Tag[]; + filtersUntoggled: boolean; + modal?: boolean; + tagsOnLeft: boolean; +} + +export function SourceSelector({ + timeRange, + filtersUntoggled, + setTimeRange, + selectedSources, + setSelectedSources, + selectedDocumentSets, + setSelectedDocumentSets, + selectedTags, + setSelectedTags, + availableDocumentSets, + existingSources, + modal, + availableTags, +}: SourceSelectorProps) { + const handleSelect = (source: SourceMetadata) => { + setSelectedSources((prev: SourceMetadata[]) => { + if ( + prev.map((source) => source.internalName).includes(source.internalName) + ) { + return prev.filter((s) => s.internalName !== source.internalName); + } else { + return [...prev, source]; + } + }); + }; + + const handleDocumentSetSelect = (documentSetName: string) => { + setSelectedDocumentSets((prev: string[]) => { + if (prev.includes(documentSetName)) { + return prev.filter((s) => s !== documentSetName); + } else { + return [...prev, documentSetName]; + } + }); + }; + + let allSourcesSelected = selectedSources.length > 0; + + const toggleAllSources = () => { + if (allSourcesSelected) { + setSelectedSources([]); + } else { + const allSources = listSourceMetadata().filter((source) => + existingSources.includes(source.internalName) + ); + setSelectedSources(allSources); + } + }; + + const [isCalendarOpen, setIsCalendarOpen] = useState(false); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + const calendar = document.querySelector(".rdp"); + if (calendar && !calendar.contains(event.target as Node)) { + setIsCalendarOpen(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, []); + + return ( +
+ {!filtersUntoggled && ( + +
+
+

Time Range

+ {timeRange && ( + + )} +
+ + + + + + { + const today = new Date(); + const initialDate = daterange?.from + ? new Date( + Math.min(daterange.from.getTime(), today.getTime()) + ) + : today; + const endDate = daterange?.to + ? new Date( + Math.min(daterange.to.getTime(), today.getTime()) + ) + : today; + setTimeRange({ + from: initialDate, + to: endDate, + selectValue: timeRange?.selectValue || "", + }); + }} + className="rounded-md" + /> + + +
+ + {availableTags.length > 0 && ( +
+ Tags + +
+ )} + + {existingSources.length > 0 && ( +
+ Sources + +
+ {existingSources.length > 1 && ( +
+ + + +
+ )} + {listSourceMetadata() + .filter((source) => + existingSources.includes(source.internalName) + ) + .map((source) => ( +
handleSelect(source)} + > + s.internalName) + .includes(source.internalName)} + /> + + {source.displayName} +
+ ))} +
+
+ )} + + {availableDocumentSets.length > 0 && ( +
+ Knowledge Sets +
+ {availableDocumentSets.map((documentSet) => ( +
handleDocumentSetSelect(documentSet.name)} + > + + + + + + + +
+
Description
+
+ {documentSet.description} +
+
+
+
+
+ {documentSet.name} +
+ ))} +
+
+ )} +
+ )} +
+ ); +} diff --git a/web/src/app/ee/admin/whitelabeling/WhitelabelingForm.tsx b/web/src/app/ee/admin/whitelabeling/WhitelabelingForm.tsx index 475c68944..cd977d44c 100644 --- a/web/src/app/ee/admin/whitelabeling/WhitelabelingForm.tsx +++ b/web/src/app/ee/admin/whitelabeling/WhitelabelingForm.tsx @@ -55,6 +55,7 @@ export function WhitelabelingForm() {
( - - )} - /> - ); -} diff --git a/web/src/app/search/page.tsx b/web/src/app/search/page.tsx deleted file mode 100644 index 3572d7cbe..000000000 --- a/web/src/app/search/page.tsx +++ /dev/null @@ -1,213 +0,0 @@ -import { - AuthTypeMetadata, - getAuthTypeMetadataSS, - getCurrentUserSS, -} from "@/lib/userSS"; -import { redirect } from "next/navigation"; -import { HealthCheckBanner } from "@/components/health/healthcheck"; -import { fetchSS } from "@/lib/utilsSS"; -import { CCPairBasicInfo, DocumentSet, Tag, User } from "@/lib/types"; -import { cookies } from "next/headers"; -import { SearchType } from "@/lib/search/interfaces"; -import { Persona } from "../admin/assistants/interfaces"; -import { unstable_noStore as noStore } from "next/cache"; -import { InstantSSRAutoRefresh } from "@/components/SSRAutoRefresh"; -import { personaComparator } from "../admin/assistants/lib"; -import { FullEmbeddingModelResponse } from "@/components/embedding/interfaces"; -import { ChatPopup } from "../chat/ChatPopup"; -import { - FetchAssistantsResponse, - fetchAssistantsSS, -} from "@/lib/assistants/fetchAssistantsSS"; -import { ChatSession } from "../chat/interfaces"; -import { SIDEBAR_TOGGLED_COOKIE_NAME } from "@/components/resizable/constants"; -import { - AGENTIC_SEARCH_TYPE_COOKIE_NAME, - NEXT_PUBLIC_DEFAULT_SIDEBAR_OPEN, - DISABLE_LLM_DOC_RELEVANCE, -} from "@/lib/constants"; -import WrappedSearch from "./WrappedSearch"; -import { SearchProvider } from "@/components/context/SearchContext"; -import { fetchLLMProvidersSS } from "@/lib/llm/fetchLLMs"; -import { LLMProviderDescriptor } from "../admin/configuration/llm/interfaces"; -import { headers } from "next/headers"; -import { - hasCompletedWelcomeFlowSS, - WelcomeModal, -} from "@/components/initialSetup/welcome/WelcomeModalWrapper"; - -export default async function Home(props: { - searchParams: Promise<{ [key: string]: string | string[] | undefined }>; -}) { - const searchParams = await props.searchParams; - // Disable caching so we always get the up to date connector / document set / persona info - // importantly, this prevents users from adding a connector, going back to the main page, - // and then getting hit with a "No Connectors" popup - noStore(); - const requestCookies = await cookies(); - const tasks = [ - getAuthTypeMetadataSS(), - getCurrentUserSS(), - fetchSS("/manage/indexing-status"), - fetchSS("/manage/document-set"), - fetchAssistantsSS(), - fetchSS("/query/valid-tags"), - fetchSS("/query/user-searches"), - fetchLLMProvidersSS(), - ]; - - // catch cases where the backend is completely unreachable here - // without try / catch, will just raise an exception and the page - // will not render - let results: ( - | User - | Response - | AuthTypeMetadata - | FullEmbeddingModelResponse - | FetchAssistantsResponse - | LLMProviderDescriptor[] - | null - )[] = [null, null, null, null, null, null, null, null]; - try { - results = await Promise.all(tasks); - } catch (e) { - console.log(`Some fetch failed for the main search page - ${e}`); - } - const authTypeMetadata = results[0] as AuthTypeMetadata | null; - const user = results[1] as User | null; - const ccPairsResponse = results[2] as Response | null; - const documentSetsResponse = results[3] as Response | null; - const [initialAssistantsList, assistantsFetchError] = - results[4] as FetchAssistantsResponse; - const tagsResponse = results[5] as Response | null; - const queryResponse = results[6] as Response | null; - const llmProviders = (results[7] || []) as LLMProviderDescriptor[]; - - const authDisabled = authTypeMetadata?.authType === "disabled"; - - if (!authDisabled && !user) { - const headersList = await headers(); - const fullUrl = headersList.get("x-url") || "/search"; - const searchParamsString = new URLSearchParams( - searchParams as unknown as Record - ).toString(); - const redirectUrl = searchParamsString - ? `${fullUrl}?${searchParamsString}` - : fullUrl; - return redirect(`/auth/login?next=${encodeURIComponent(redirectUrl)}`); - } - - if (user && !user.is_verified && authTypeMetadata?.requiresVerification) { - return redirect("/auth/waiting-on-verification"); - } - - let ccPairs: CCPairBasicInfo[] = []; - if (ccPairsResponse?.ok) { - ccPairs = await ccPairsResponse.json(); - } else { - console.log(`Failed to fetch connectors - ${ccPairsResponse?.status}`); - } - - let documentSets: DocumentSet[] = []; - if (documentSetsResponse?.ok) { - documentSets = await documentSetsResponse.json(); - } else { - console.log( - `Failed to fetch document sets - ${documentSetsResponse?.status}` - ); - } - - let querySessions: ChatSession[] = []; - if (queryResponse?.ok) { - querySessions = (await queryResponse.json()).sessions; - } else { - console.log(`Failed to fetch chat sessions - ${queryResponse?.text()}`); - } - - let assistants: Persona[] = initialAssistantsList; - if (assistantsFetchError) { - console.log(`Failed to fetch assistants - ${assistantsFetchError}`); - } else { - // remove those marked as hidden by an admin - assistants = assistants.filter((assistant) => assistant.is_visible); - // hide personas with no retrieval - assistants = assistants.filter((assistant) => assistant.num_chunks !== 0); - // sort them in priority order - assistants.sort(personaComparator); - } - - let tags: Tag[] = []; - if (tagsResponse?.ok) { - tags = (await tagsResponse.json()).tags; - } else { - console.log(`Failed to fetch tags - ${tagsResponse?.status}`); - } - - // needs to be done in a non-client side component due to nextjs - const storedSearchType = requestCookies.get("searchType")?.value as - | string - | undefined; - const searchTypeDefault: SearchType = - storedSearchType !== undefined && - SearchType.hasOwnProperty(storedSearchType) - ? (storedSearchType as SearchType) - : SearchType.SEMANTIC; // default to semantic - - const hasAnyConnectors = ccPairs.length > 0; - - const shouldShowWelcomeModal = - !llmProviders.length && - !hasCompletedWelcomeFlowSS(requestCookies) && - !hasAnyConnectors && - (!user || user.role === "admin"); - - const shouldDisplayNoSourcesModal = - (!user || user.role === "admin") && - ccPairs.length === 0 && - !shouldShowWelcomeModal; - - const sidebarToggled = requestCookies.get(SIDEBAR_TOGGLED_COOKIE_NAME); - const agenticSearchToggle = requestCookies.get( - AGENTIC_SEARCH_TYPE_COOKIE_NAME - ); - - const toggleSidebar = sidebarToggled - ? sidebarToggled.value.toLocaleLowerCase() == "true" || false - : NEXT_PUBLIC_DEFAULT_SIDEBAR_OPEN; - - const agenticSearchEnabled = agenticSearchToggle - ? agenticSearchToggle.value.toLocaleLowerCase() == "true" || false - : false; - - return ( - <> - - - {shouldShowWelcomeModal && ( - - )} - {/* ChatPopup is a custom popup that displays a admin-specified message on initial user visit. - Only used in the EE version of the app. */} - - - - - - ); -} diff --git a/web/src/components/InternetSearchIcon.tsx b/web/src/components/InternetSearchIcon.tsx deleted file mode 100644 index e21218da9..000000000 --- a/web/src/components/InternetSearchIcon.tsx +++ /dev/null @@ -1,9 +0,0 @@ -export function InternetSearchIcon({ url }: { url: string }) { - return ( - favicon - ); -} diff --git a/web/src/components/MetadataBadge.tsx b/web/src/components/MetadataBadge.tsx index cfd94d0a8..f06429a92 100644 --- a/web/src/components/MetadataBadge.tsx +++ b/web/src/components/MetadataBadge.tsx @@ -1,9 +1,11 @@ export function MetadataBadge({ icon, value, + flexNone, }: { icon?: React.FC<{ size?: number; className?: string }>; value: string | JSX.Element; + flexNone?: boolean; }) { return (
- {icon && icon({ size: 12, className: "mr-0.5 my-auto" })} + {icon && + icon({ + size: 12, + className: flexNone ? "flex-none" : "mr-0.5 my-auto", + })}
{value}
); diff --git a/web/src/components/Modal.tsx b/web/src/components/Modal.tsx index 058869750..7175d46cb 100644 --- a/web/src/components/Modal.tsx +++ b/web/src/components/Modal.tsx @@ -1,11 +1,11 @@ "use client"; import { Separator } from "@/components/ui/separator"; -import { FiX } from "react-icons/fi"; import { IconProps, XIcon } from "./icons/icons"; import { useRef } from "react"; import { isEventWithinRef } from "@/lib/contains"; import ReactDOM from "react-dom"; import { useEffect, useState } from "react"; +import { cn } from "@/lib/utils"; interface ModalProps { icon?: ({ size, className }: IconProps) => JSX.Element; @@ -18,6 +18,8 @@ interface ModalProps { hideDividerForTitle?: boolean; hideCloseButton?: boolean; noPadding?: boolean; + height?: string; + noScroll?: boolean; } export function Modal({ @@ -28,9 +30,11 @@ export function Modal({ width, titleSize, hideDividerForTitle, + height, noPadding, icon, hideCloseButton, + noScroll, }: ModalProps) { const modalRef = useRef(null); const [isMounted, setIsMounted] = useState(false); @@ -56,8 +60,10 @@ export function Modal({ const modalContent = (
)} - -
+
{title && ( <>
@@ -110,7 +115,14 @@ export function Modal({ {!hideDividerForTitle && } )} -
{children}
+
+ {children} +
diff --git a/web/src/components/SearchResultIcon.tsx b/web/src/components/SearchResultIcon.tsx new file mode 100644 index 000000000..28aee0578 --- /dev/null +++ b/web/src/components/SearchResultIcon.tsx @@ -0,0 +1,65 @@ +import { useState, useEffect } from "react"; +import faviconFetch from "favicon-fetch"; +import { SourceIcon } from "./SourceIcon"; + +const CACHE_DURATION = 24 * 60 * 60 * 1000; + +export async function getFaviconUrl(url: string): Promise { + const getCachedFavicon = () => { + const cachedData = localStorage.getItem(`favicon_${url}`); + if (cachedData) { + const { favicon, timestamp } = JSON.parse(cachedData); + if (Date.now() - timestamp < CACHE_DURATION) { + return favicon; + } + } + return null; + }; + + const cachedFavicon = getCachedFavicon(); + if (cachedFavicon) { + return cachedFavicon; + } + + const newFaviconUrl = await faviconFetch({ uri: url }); + if (newFaviconUrl) { + localStorage.setItem( + `favicon_${url}`, + JSON.stringify({ favicon: newFaviconUrl, timestamp: Date.now() }) + ); + return newFaviconUrl; + } + + return null; +} + +export function SearchResultIcon({ url }: { url: string }) { + const [faviconUrl, setFaviconUrl] = useState(null); + + useEffect(() => { + getFaviconUrl(url).then((favicon) => { + if (favicon) { + setFaviconUrl(favicon); + } + }); + }, [url]); + + if (!faviconUrl) { + return ; + } + + return ( +
+ favicon { + e.currentTarget.onerror = null; + }} + /> +
+ ); +} diff --git a/web/src/components/UserDropdown.tsx b/web/src/components/UserDropdown.tsx index 8a1503410..cc43980b8 100644 --- a/web/src/components/UserDropdown.tsx +++ b/web/src/components/UserDropdown.tsx @@ -9,7 +9,7 @@ import { checkUserIsNoAuthUser, logout } from "@/lib/user"; import { Popover } from "./popover/Popover"; import { LOGOUT_DISABLED } from "@/lib/constants"; import { SettingsContext } from "./settings/SettingsProvider"; -import { BellIcon, LightSettingsIcon } from "./icons/icons"; +import { BellIcon, LightSettingsIcon, UserIcon } from "./icons/icons"; import { pageType } from "@/app/chat/sessionSidebar/types"; import { NavigationItem, Notification } from "@/app/admin/settings/interfaces"; import DynamicFaIcon, { preloadIcons } from "./icons/DynamicFaIcon"; @@ -56,7 +56,13 @@ const DropdownOption: React.FC = ({ } }; -export function UserDropdown({ page }: { page?: pageType }) { +export function UserDropdown({ + page, + toggleUserSettings, +}: { + page?: pageType; + toggleUserSettings?: () => void; +}) { const { user, isCurator } = useUser(); const [userInfoVisible, setUserInfoVisible] = useState(false); const userInfoRef = useRef(null); @@ -238,6 +244,13 @@ export function UserDropdown({ page }: { page?: pageType }) { ) )} + {toggleUserSettings && ( + } + label="User Settings" + /> + )} { setUserInfoVisible(true); diff --git a/web/src/components/WebResultIcon.tsx b/web/src/components/WebResultIcon.tsx new file mode 100644 index 000000000..27e5e91ee --- /dev/null +++ b/web/src/components/WebResultIcon.tsx @@ -0,0 +1,16 @@ +import { SourceIcon } from "./SourceIcon"; + +export function WebResultIcon({ url }: { url: string }) { + const hostname = new URL(url).hostname; + return hostname == "https://docs.danswer.dev" ? ( + favicon + ) : ( + + ); +} diff --git a/web/src/components/admin/connectors/AdminSidebar.tsx b/web/src/components/admin/connectors/AdminSidebar.tsx index 26f0694a6..be1ee3de9 100644 --- a/web/src/components/admin/connectors/AdminSidebar.tsx +++ b/web/src/components/admin/connectors/AdminSidebar.tsx @@ -40,14 +40,7 @@ export function AdminSidebar({ collections }: { collections: Collection[] }) {