From 44b70a87df95f356b3892447f247292ad0036e05 Mon Sep 17 00:00:00 2001 From: pablonyx Date: Thu, 16 Jan 2025 00:08:01 -0800 Subject: [PATCH] UX Refresh (#3687) * add new ux * quick nit * additional nit * finalize * quick fix * fix typing --- ...381bce97c_add_shortcut_option_for_users.py | 29 + .../3c6531f32351_add_back_input_prompts.py | 59 + .../47e5bef3a1d7_add_persona_categories.py | 2 +- ...make_categories_labels_and_many_to_many.py | 80 + .../aeda5f2df4f6_add_pinned_assistants.py | 27 + backend/chatt.txt | 536 +++++ backend/onyx/auth/noauth_user.py | 1 + backend/onyx/configs/chat_configs.py | 2 +- backend/onyx/db/input_prompt.py | 262 +++ backend/onyx/db/models.py | 67 +- backend/onyx/db/persona.py | 52 +- backend/onyx/main.py | 8 + backend/onyx/prompts/starter_messages.py | 34 +- .../starter_message_creation.py | 148 +- backend/onyx/seeding/input_prompts.yaml | 24 + backend/onyx/seeding/load_yamls.py | 29 +- backend/onyx/server/documents/document.py | 1 + .../onyx/server/features/input_prompt/api.py | 156 ++ .../server/features/input_prompt/models.py | 47 + backend/onyx/server/features/persona/api.py | 75 +- .../onyx/server/features/persona/models.py | 33 +- backend/onyx/server/manage/models.py | 4 + backend/onyx/server/manage/users.py | 55 + .../images/image_generation_tool.py | 2 +- .../internet_search/internet_search_tool.py | 2 +- .../common_utils/managers/persona.py | 51 +- .../integration/common_utils/test_models.py | 3 +- .../tests/personas/test_persona_categories.py | 85 +- node_modules/.package-lock.json | 6 - package-lock.json | 183 ++ package.json | 8 + web/package-lock.json | 541 ++++- web/package.json | 2 + web/public/logo.svg | 5 + web/public/web.svg | 7 + web/src/app/admin/add-connector/page.tsx | 2 +- .../app/admin/assistants/AssistantEditor.tsx | 1875 +++++++++-------- .../admin/assistants/CollapsibleSection.tsx | 2 +- .../{CategoryCard.tsx => LabelCard.tsx} | 44 +- .../admin/assistants/StarterMessageList.tsx | 278 +-- web/src/app/admin/assistants/[id]/page.tsx | 18 +- web/src/app/admin/assistants/interfaces.ts | 11 +- web/src/app/admin/assistants/lib.ts | 30 +- web/src/app/admin/assistants/new/page.tsx | 13 +- .../app/admin/configuration/llm/interfaces.ts | 3 +- .../status/CCPairIndexingStatusTable.tsx | 2 +- web/src/app/admin/layout.tsx | 1 + .../app/admin/settings/AnonymousUserPath.tsx | 2 - .../app/assistants/AssistantSharedStatus.tsx | 14 +- .../app/assistants/AssistantsPageTitle.tsx | 19 - web/src/app/assistants/NavigationButton.tsx | 25 - web/src/app/assistants/PersonaCategory.tsx | 28 - web/src/app/assistants/SidebarWrapper.tsx | 45 +- web/src/app/assistants/edit/[id]/page.tsx | 41 +- .../assistants/gallery/AssistantsGallery.tsx | 405 ---- .../gallery/WrappedAssistantsGallery.tsx | 16 - web/src/app/assistants/gallery/page.tsx | 64 - web/src/app/assistants/mine/AssistantCard.tsx | 327 +++ .../app/assistants/mine/AssistantModal.tsx | 237 +++ .../assistants/mine/AssistantSharingModal.tsx | 2 +- .../mine/AssistantSharingPopover.tsx | 213 ++ .../mine/AssistantVisibilityPopover.tsx | 198 ++ .../app/assistants/mine/AssistantsList.tsx | 498 ----- .../mine/DeleteAssistantPopover.tsx | 31 + .../mine/MakePublicAssistantPopover.tsx | 69 + .../assistants/mine/WrappedAssistantsMine.tsx | 15 - web/src/app/assistants/mine/page.tsx | 64 - web/src/app/assistants/new/page.tsx | 23 +- web/src/app/chat/ChatBanner.tsx | 116 +- web/src/app/chat/ChatIntro.tsx | 36 +- web/src/app/chat/ChatPage.tsx | 300 +-- web/src/app/chat/RegenerateOption.tsx | 8 +- web/src/app/chat/WrappedChat.tsx | 7 +- .../documentSidebar/ChatDocumentDisplay.tsx | 7 +- .../app/chat/documentSidebar/ChatFilters.tsx | 200 -- .../chat/documentSidebar/DocumentResults.tsx | 166 ++ web/src/app/chat/files/InputBarPreview.tsx | 47 +- .../chat/files/documents/DocumentPreview.tsx | 19 +- .../files/images/InputBarPreviewImage.tsx | 5 +- web/src/app/chat/folders/FolderDropdown.tsx | 274 +++ web/src/app/chat/folders/FolderList.tsx | 15 +- web/src/app/chat/folders/FolderManagement.tsx | 16 + web/src/app/chat/folders/interfaces.ts | 2 +- .../app/chat/input-prompts/InputPrompts.tsx | 313 +++ web/src/app/chat/input-prompts/page.tsx | 15 + web/src/app/chat/input/ChatInputBar.tsx | 513 +++-- web/src/app/chat/input/ChatInputOption.tsx | 112 +- web/src/app/chat/input/LLMPopover.tsx | 153 ++ .../app/chat/input/SimplifiedChatInputBar.tsx | 2 +- web/src/app/chat/interfaces.ts | 32 + web/src/app/chat/layout.tsx | 4 + .../chat/message/MemoizedTextComponents.tsx | 29 +- web/src/app/chat/message/Messages.tsx | 45 +- web/src/app/chat/message/SearchSummary.tsx | 8 +- web/src/app/chat/message/SkippedSearch.tsx | 23 +- web/src/app/chat/modal/FeedbackModal.tsx | 6 +- web/src/app/chat/modal/Inpu | 0 .../modal/InputPromptCreationComponents.tsx | 0 .../app/chat/modal/InputPromptsSection.tsx | 285 +++ ...ltModelModal.tsx => UserSettingsModal.tsx} | 124 +- web/src/app/chat/page.tsx | 2 +- web/src/app/chat/sessionSidebar/ChatGroup.tsx | 20 + .../sessionSidebar/ChatSessionDisplay.tsx | 405 ++-- .../chat/sessionSidebar/HistorySidebar.tsx | 292 ++- web/src/app/chat/sessionSidebar/PagesTab.tsx | 549 +++-- web/src/app/chat/sessionSidebar/asf.html | 40 + .../shared/[chatId]/SharedChatDisplay.tsx | 18 +- .../admin/whitelabeling/WhitelabelingForm.tsx | 4 +- .../assistants/stats/[id]/AssistantStats.tsx | 8 +- .../stats/[id]/WrappedAssistantsStats.tsx | 2 +- web/src/app/ee/assistants/stats/[id]/page.tsx | 22 +- web/src/app/globals.css | 8 +- web/src/app/layout.tsx | 15 +- web/src/components/BasicClickable.tsx | 20 +- web/src/components/Dropdown.tsx | 100 +- web/src/components/Hoverable.tsx | 2 +- web/src/components/Modal.tsx | 10 +- web/src/components/OnyxInitializingLoader.tsx | 2 +- web/src/components/UserDropdown.tsx | 35 +- web/src/components/WebResultIcon.tsx | 30 +- web/src/components/admin/CardSection.tsx | 7 +- web/src/components/admin/ClientLayout.tsx | 27 +- web/src/components/admin/Layout.tsx | 55 +- web/src/components/admin/connectors/Field.tsx | 47 +- .../components/assistants/AssistantBanner.tsx | 122 -- .../assistants/AssistantDescriptionCard.tsx | 63 - .../components/assistants/AssistantIcon.tsx | 227 +- .../components/assistants/StarterMessage.tsx | 29 +- web/src/components/auth/AuthFlowContainer.tsx | 2 +- web/src/components/chat_search/Header.tsx | 92 +- .../chat_search/sources/SourceCard.tsx | 47 +- web/src/components/context/AppProvider.tsx | 3 + .../components/context/AssistantsContext.tsx | 27 + web/src/components/context/ChatContext.tsx | 43 +- web/src/components/header/LogoWithText.tsx | 6 +- web/src/components/icons/icons.tsx | 381 +++- web/src/components/llm/LLMList.tsx | 32 +- web/src/components/llm/LLMSelector.tsx | 91 + .../components/modals/DeleteEntityModal.tsx | 14 +- web/src/components/popover/Popover.tsx | 20 +- web/src/components/search/DocumentDisplay.tsx | 8 +- .../search/filtering/FilterPopup.tsx | 420 ++++ .../components/search/results/Citation.tsx | 3 +- web/src/components/tooltip/CustomTooltip.tsx | 7 +- web/src/components/ui/SlideOverModal.tsx | 91 + web/src/components/ui/button.tsx | 2 +- web/src/components/ui/checkbox.tsx | 43 +- web/src/components/ui/dropdown-menu.tsx | 203 ++ web/src/components/ui/select.tsx | 17 +- web/src/components/ui/switch.tsx | 54 +- web/src/components/ui/tooltip.tsx | 4 +- web/src/components/user/UserProvider.tsx | 35 + web/src/hooks/input-prompts.ts | 46 + web/src/lib/assistants/pinnedAssistants.tsx | 39 + .../assistants/updateAssistantPreferences.ts | 2 + web/src/lib/assistants/utils.ts | 2 +- web/src/lib/chat/fetchChatData.ts | 17 +- web/src/lib/dateUtils.ts | 17 + web/src/lib/dropdown.ts | 17 +- web/src/lib/hooks.ts | 24 +- web/src/lib/sources.ts | 6 +- web/src/lib/types.ts | 2 + web/tailwind-themes/tailwind.config.js | 36 +- 163 files changed, 9741 insertions(+), 4396 deletions(-) create mode 100644 backend/alembic/versions/027381bce97c_add_shortcut_option_for_users.py create mode 100644 backend/alembic/versions/3c6531f32351_add_back_input_prompts.py create mode 100644 backend/alembic/versions/6fc7886d665d_make_categories_labels_and_many_to_many.py create mode 100644 backend/alembic/versions/aeda5f2df4f6_add_pinned_assistants.py create mode 100644 backend/chatt.txt create mode 100644 backend/onyx/db/input_prompt.py create mode 100644 backend/onyx/seeding/input_prompts.yaml create mode 100644 backend/onyx/server/features/input_prompt/api.py create mode 100644 backend/onyx/server/features/input_prompt/models.py delete mode 100644 node_modules/.package-lock.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 web/public/logo.svg create mode 100644 web/public/web.svg rename web/src/app/admin/assistants/{CategoryCard.tsx => LabelCard.tsx} (64%) delete mode 100644 web/src/app/assistants/AssistantsPageTitle.tsx delete mode 100644 web/src/app/assistants/NavigationButton.tsx delete mode 100644 web/src/app/assistants/PersonaCategory.tsx delete mode 100644 web/src/app/assistants/gallery/AssistantsGallery.tsx delete mode 100644 web/src/app/assistants/gallery/WrappedAssistantsGallery.tsx delete mode 100644 web/src/app/assistants/gallery/page.tsx create mode 100644 web/src/app/assistants/mine/AssistantCard.tsx create mode 100644 web/src/app/assistants/mine/AssistantModal.tsx create mode 100644 web/src/app/assistants/mine/AssistantSharingPopover.tsx create mode 100644 web/src/app/assistants/mine/AssistantVisibilityPopover.tsx delete mode 100644 web/src/app/assistants/mine/AssistantsList.tsx create mode 100644 web/src/app/assistants/mine/DeleteAssistantPopover.tsx create mode 100644 web/src/app/assistants/mine/MakePublicAssistantPopover.tsx delete mode 100644 web/src/app/assistants/mine/WrappedAssistantsMine.tsx delete mode 100644 web/src/app/assistants/mine/page.tsx delete mode 100644 web/src/app/chat/documentSidebar/ChatFilters.tsx create mode 100644 web/src/app/chat/documentSidebar/DocumentResults.tsx create mode 100644 web/src/app/chat/folders/FolderDropdown.tsx create mode 100644 web/src/app/chat/input-prompts/InputPrompts.tsx create mode 100644 web/src/app/chat/input-prompts/page.tsx create mode 100644 web/src/app/chat/input/LLMPopover.tsx create mode 100644 web/src/app/chat/modal/Inpu create mode 100644 web/src/app/chat/modal/InputPromptCreationComponents.tsx create mode 100644 web/src/app/chat/modal/InputPromptsSection.tsx rename web/src/app/chat/modal/{SetDefaultModelModal.tsx => UserSettingsModal.tsx} (66%) create mode 100644 web/src/app/chat/sessionSidebar/ChatGroup.tsx create mode 100644 web/src/app/chat/sessionSidebar/asf.html delete mode 100644 web/src/components/assistants/AssistantBanner.tsx delete mode 100644 web/src/components/assistants/AssistantDescriptionCard.tsx create mode 100644 web/src/components/llm/LLMSelector.tsx create mode 100644 web/src/components/search/filtering/FilterPopup.tsx create mode 100644 web/src/components/ui/SlideOverModal.tsx create mode 100644 web/src/components/ui/dropdown-menu.tsx create mode 100644 web/src/hooks/input-prompts.ts create mode 100644 web/src/lib/assistants/pinnedAssistants.tsx diff --git a/backend/alembic/versions/027381bce97c_add_shortcut_option_for_users.py b/backend/alembic/versions/027381bce97c_add_shortcut_option_for_users.py new file mode 100644 index 00000000000..7e6c71eb972 --- /dev/null +++ b/backend/alembic/versions/027381bce97c_add_shortcut_option_for_users.py @@ -0,0 +1,29 @@ +"""add shortcut option for users + +Revision ID: 027381bce97c +Revises: 6fc7886d665d +Create Date: 2025-01-14 12:14:00.814390 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "027381bce97c" +down_revision = "6fc7886d665d" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column( + "user", + sa.Column( + "shortcut_enabled", sa.Boolean(), nullable=False, server_default="false" + ), + ) + + +def downgrade() -> None: + op.drop_column("user", "shortcut_enabled") diff --git a/backend/alembic/versions/3c6531f32351_add_back_input_prompts.py b/backend/alembic/versions/3c6531f32351_add_back_input_prompts.py new file mode 100644 index 00000000000..a115101b9ec --- /dev/null +++ b/backend/alembic/versions/3c6531f32351_add_back_input_prompts.py @@ -0,0 +1,59 @@ +"""add back input prompts + +Revision ID: 3c6531f32351 +Revises: aeda5f2df4f6 +Create Date: 2025-01-13 12:49:51.705235 + +""" +from alembic import op +import sqlalchemy as sa +import fastapi_users_db_sqlalchemy + +# revision identifiers, used by Alembic. +revision = "3c6531f32351" +down_revision = "aeda5f2df4f6" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.create_table( + "inputprompt", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("prompt", sa.String(), nullable=False), + sa.Column("content", sa.String(), nullable=False), + sa.Column("active", sa.Boolean(), nullable=False), + sa.Column("is_public", sa.Boolean(), nullable=False), + sa.Column( + "user_id", + fastapi_users_db_sqlalchemy.generics.GUID(), + nullable=True, + ), + sa.ForeignKeyConstraint( + ["user_id"], + ["user.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "inputprompt__user", + sa.Column("input_prompt_id", sa.Integer(), nullable=False), + sa.Column( + "user_id", fastapi_users_db_sqlalchemy.generics.GUID(), nullable=False + ), + sa.Column("disabled", sa.Boolean(), nullable=False, default=False), + sa.ForeignKeyConstraint( + ["input_prompt_id"], + ["inputprompt.id"], + ), + sa.ForeignKeyConstraint( + ["user_id"], + ["user.id"], + ), + sa.PrimaryKeyConstraint("input_prompt_id", "user_id"), + ) + + +def downgrade() -> None: + op.drop_table("inputprompt__user") + op.drop_table("inputprompt") diff --git a/backend/alembic/versions/47e5bef3a1d7_add_persona_categories.py b/backend/alembic/versions/47e5bef3a1d7_add_persona_categories.py index 432e0ab42a5..bd1ba3eb2d4 100644 --- a/backend/alembic/versions/47e5bef3a1d7_add_persona_categories.py +++ b/backend/alembic/versions/47e5bef3a1d7_add_persona_categories.py @@ -40,6 +40,6 @@ def upgrade() -> None: def downgrade() -> None: - op.drop_constraint("fk_persona_category", "persona", type_="foreignkey") + op.drop_constraint("persona_category_id_fkey", "persona", type_="foreignkey") op.drop_column("persona", "category_id") op.drop_table("persona_category") diff --git a/backend/alembic/versions/6fc7886d665d_make_categories_labels_and_many_to_many.py b/backend/alembic/versions/6fc7886d665d_make_categories_labels_and_many_to_many.py new file mode 100644 index 00000000000..eaaacdd4ed9 --- /dev/null +++ b/backend/alembic/versions/6fc7886d665d_make_categories_labels_and_many_to_many.py @@ -0,0 +1,80 @@ +"""make categories labels and many to many + +Revision ID: 6fc7886d665d +Revises: 3c6531f32351 +Create Date: 2025-01-13 18:12:18.029112 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "6fc7886d665d" +down_revision = "3c6531f32351" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # Rename persona_category table to persona_label + op.rename_table("persona_category", "persona_label") + + # Create the new association table + op.create_table( + "persona__persona_label", + sa.Column("persona_id", sa.Integer(), nullable=False), + sa.Column("persona_label_id", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["persona_id"], + ["persona.id"], + ), + sa.ForeignKeyConstraint( + ["persona_label_id"], + ["persona_label.id"], + ondelete="CASCADE", + ), + sa.PrimaryKeyConstraint("persona_id", "persona_label_id"), + ) + + # Copy existing relationships to the new table + op.execute( + """ + INSERT INTO persona__persona_label (persona_id, persona_label_id) + SELECT id, category_id FROM persona WHERE category_id IS NOT NULL + """ + ) + + # Remove the old category_id column from persona table + op.drop_column("persona", "category_id") + + +def downgrade() -> None: + # Rename persona_label table back to persona_category + op.rename_table("persona_label", "persona_category") + + # Add back the category_id column to persona table + op.add_column("persona", sa.Column("category_id", sa.Integer(), nullable=True)) + op.create_foreign_key( + "persona_category_id_fkey", + "persona", + "persona_category", + ["category_id"], + ["id"], + ) + + # Copy the first label relationship back to the persona table + op.execute( + """ + UPDATE persona + SET category_id = ( + SELECT persona_label_id + FROM persona__persona_label + WHERE persona__persona_label.persona_id = persona.id + LIMIT 1 + ) + """ + ) + + # Drop the association table + op.drop_table("persona__persona_label") diff --git a/backend/alembic/versions/aeda5f2df4f6_add_pinned_assistants.py b/backend/alembic/versions/aeda5f2df4f6_add_pinned_assistants.py new file mode 100644 index 00000000000..a0c5d69ffaf --- /dev/null +++ b/backend/alembic/versions/aeda5f2df4f6_add_pinned_assistants.py @@ -0,0 +1,27 @@ +"""add pinned assistants + +Revision ID: aeda5f2df4f6 +Revises: c5eae4a75a1b +Create Date: 2025-01-09 16:04:10.770636 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "aeda5f2df4f6" +down_revision = "c5eae4a75a1b" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column( + "user", sa.Column("pinned_assistants", postgresql.JSONB(), nullable=True) + ) + op.execute('UPDATE "user" SET pinned_assistants = chosen_assistants') + + +def downgrade() -> None: + op.drop_column("user", "pinned_assistants") diff --git a/backend/chatt.txt b/backend/chatt.txt new file mode 100644 index 00000000000..b5daadcce91 --- /dev/null +++ b/backend/chatt.txt @@ -0,0 +1,536 @@ +"{\"user_message_id\": 475, \"reserved_assistant_message_id\": 476}\n" +"{\"sub_question\": \"What\", \"level\": 0, \"level_question_nr\": 1}\n" +"{\"sub_query\": \"ony\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 0}\n" +"{\"sub_question\": \" is\", \"level\": 0, \"level_question_nr\": 1}\n" +"{\"sub_query\": \"x\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 0}\n" +"{\"sub_question\": \" On\", \"level\": 0, \"level_question_nr\": 1}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 0}\n" +"{\"sub_question\": \"yx\", \"level\": 0, \"level_question_nr\": 1}\n" +"{\"sub_question\": \" \", \"level\": 0, \"level_question_nr\": 1}\n" +"{\"sub_query\": \"1\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 0}\n" +"{\"sub_question\": \"1\", \"level\": 0, \"level_question_nr\": 1}\n" +"{\"sub_query\": \" features\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 0}\n" +"{\"sub_query\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 0}\n" +"{\"sub_question\": \"?\", \"level\": 0, \"level_question_nr\": 1}\n" +"{\"sub_question\": \" \", \"level\": 0, \"level_question_nr\": 1}\n" +"{\"sub_question\": \"\", \"level\": 0, \"level_question_nr\": 2}\n" +"{\"sub_question\": \"What\", \"level\": 0, \"level_question_nr\": 2}\n" +"{\"sub_question\": \" is\", \"level\": 0, \"level_question_nr\": 2}\n" +"{\"sub_query\": \" specifications\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 0}\n" +"{\"sub_question\": \" On\", \"level\": 0, \"level_question_nr\": 2}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 0}\n" +"{\"sub_query\": \"\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_question\": \"yx\", \"level\": 0, \"level_question_nr\": 2}\n" +"{\"sub_question\": \" \", \"level\": 0, \"level_question_nr\": 2}\n" +"{\"sub_question\": \"2\", \"level\": 0, \"level_question_nr\": 2}\n" +"{\"sub_query\": \"ony\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_question\": \"?\", \"level\": 0, \"level_question_nr\": 2}\n" +"{\"sub_query\": \"x\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_question\": \" \", \"level\": 0, \"level_question_nr\": 2}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_question\": \"\", \"level\": 0, \"level_question_nr\": 3}\n" +"{\"sub_query\": \"2\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_question\": \"What\", \"level\": 0, \"level_question_nr\": 3}\n" +"{\"sub_query\": \" applications\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_question\": \" is\", \"level\": 0, \"level_question_nr\": 3}\n" +"{\"sub_query\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_question\": \" On\", \"level\": 0, \"level_question_nr\": 3}\n" +"{\"sub_query\": \" use\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_question\": \"yx\", \"level\": 0, \"level_question_nr\": 3}\n" +"{\"sub_query\": \" cases\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_question\": \" \", \"level\": 0, \"level_question_nr\": 3}\n" +"{\"sub_question\": \"3\", \"level\": 0, \"level_question_nr\": 3}\n" +"{\"sub_question\": \"?\", \"level\": 0, \"level_question_nr\": 3}\n" +"{\"sub_question\": \" \", \"level\": 0, \"level_question_nr\": 3}\n" +"{\"sub_question\": \"\", \"level\": 0, \"level_question_nr\": 4}\n" +"{\"sub_question\": \"What\", \"level\": 0, \"level_question_nr\": 4}\n" +"{\"sub_question\": \" is\", \"level\": 0, \"level_question_nr\": 4}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_question\": \" On\", \"level\": 0, \"level_question_nr\": 4}\n" +"{\"sub_query\": \"\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_question\": \"yx\", \"level\": 0, \"level_question_nr\": 4}\n" +"{\"sub_question\": \" \", \"level\": 0, \"level_question_nr\": 4}\n" +"{\"sub_query\": \"ony\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_question\": \"4\", \"level\": 0, \"level_question_nr\": 4}\n" +"{\"sub_question\": \"?\", \"level\": 0, \"level_question_nr\": 4}\n" +"{\"sub_query\": \"x\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_question\": \" \", \"level\": 0, \"level_question_nr\": 4}\n" +"{\"sub_question\": \"\", \"level\": 0, \"level_question_nr\": 4}\n" +"{\"sub_query\": \"3\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \"4\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \" comparison\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \" differences\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \"\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \"On\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 0}\n" +"{\"sub_query\": \"yx\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 0}\n" +"{\"sub_query\": \"On\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 0}\n" +"{\"sub_query\": \"yx\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 0}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 0}\n" +"{\"sub_query\": \"4\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 0}\n" +"{\"sub_query\": \"On\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 0}\n" +"{\"sub_query\": \"yx\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 0}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 0}\n" +"{\"sub_query\": \"1\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 0}\n" +"{\"sub_query\": \"On\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 0}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 0}\n" +"{\"sub_query\": \"yx\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 0}\n" +"{\"sub_query\": \" product\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 0}\n" +"{\"sub_query\": \"3\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 0}\n" +"{\"sub_query\": \" information\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 0}\n" +"{\"sub_query\": \" software\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 0}\n" +"{\"sub_query\": \" features\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 0}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 0}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 0}\n" +"{\"sub_query\": \" software\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 0}\n" +"{\"sub_query\": \"2\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 0}\n" +"{\"sub_query\": \"\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 1}\n" +"{\"sub_query\": \" features\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 0}\n" +"{\"sub_query\": \"On\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 1}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 0}\n" +"{\"sub_query\": \"\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_query\": \"On\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_query\": \"yx\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 1}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 1}\n" +"{\"sub_query\": \"yx\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_query\": \" software\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 0}\n" +"{\"sub_query\": \" features\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 0}\n" +"{\"sub_query\": \"4\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 1}\n" +"{\"sub_query\": \" applications\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 1}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 0}\n" +"{\"sub_query\": \"1\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_query\": \"\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 1}\n" +"{\"sub_query\": \" features\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_query\": \"On\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 1}\n" +"{\"sub_query\": \" in\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 1}\n" +"{\"sub_query\": \" industry\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 1}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 0}\n" +"{\"sub_query\": \"\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 1}\n" +"{\"sub_query\": \"yx\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 1}\n" +"{\"sub_query\": \"On\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 1}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 1}\n" +"{\"sub_query\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_query\": \"yx\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 1}\n" +"{\"sub_query\": \" specifications\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 1}\n" +"{\"sub_query\": \"2\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 1}\n" +"{\"sub_query\": \" applications\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 1}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 1}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 1}\n" +"{\"sub_query\": \"\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 2}\n" +"{\"sub_query\": \"\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \"On\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 2}\n" +"{\"sub_query\": \"On\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \"3\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 1}\n" +"{\"sub_query\": \"yx\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \" applications\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 1}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \"yx\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 2}\n" +"{\"sub_query\": \" in\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 1}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 2}\n" +"{\"sub_query\": \" industry\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 1}\n" +"{\"sub_query\": \"1\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \" applications\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 1}\n" +"{\"sub_query\": \"\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 2}\n" +"{\"sub_query\": \"On\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 2}\n" +"{\"sub_query\": \"4\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 2}\n" +"{\"sub_query\": \" comparison\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 2}\n" +"{\"sub_query\": \" with\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 2}\n" +"{\"sub_query\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \" previous\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 2}\n" +"{\"sub_query\": \" use\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \" versions\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 2}\n" +"{\"sub_query\": \" in\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 1}\n" +"{\"sub_query\": \" industry\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 1}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 2}\n" +"{\"sub_query\": \"\", \"level\": 0, \"level_question_nr\": 3, \"query_id\": 2}\n" +"{\"sub_query\": \"yx\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 2}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 1}\n" +"{\"sub_query\": \"\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 2}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 2}\n" +"{\"sub_query\": \"On\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 2}\n" +"{\"sub_query\": \"yx\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 2}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 2}\n" +"{\"sub_query\": \"3\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 2}\n" +"{\"sub_query\": \" comparison\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 2}\n" +"{\"sub_query\": \"2\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 2}\n" +"{\"sub_query\": \" comparison\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 2}\n" +"{\"sub_query\": \" with\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 2}\n" +"{\"sub_query\": \" cases\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \" previous\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 2}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \"\", \"level\": 0, \"level_question_nr\": 0, \"query_id\": 2}\n" +"{\"sub_query\": \" with\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 2}\n" +"{\"sub_query\": \" other\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 2}\n" +"{\"sub_query\": \" software\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 2}\n" +"{\"sub_query\": \"\", \"level\": 0, \"level_question_nr\": 1, \"query_id\": 2}\n" +"{\"sub_query\": \" versions\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 2}\n" +"{\"sub_query\": \" \", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 2}\n" +"{\"sub_query\": \"\", \"level\": 0, \"level_question_nr\": 2, \"query_id\": 2}\n" +"{\"top_documents\": [], \"rephrased_query\": \"What is Onyx 4?\", \"predicted_flow\": \"question-answer\", \"predicted_search\": \"keyword\", \"applied_source_filters\": null, \"applied_time_cutoff\": null, \"recency_bias_multiplier\": 0.5}\n" +"{\"llm_selected_doc_indices\": []}\n" +"{\"final_context_docs\": []}\n" +"{\"answer_piece\": \"I\", \"level\": 0, \"level_question_nr\": 3, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" don't\", \"level\": 0, \"level_question_nr\": 3, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \"On\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" know\", \"level\": 0, \"level_question_nr\": 3, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \"yx\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \".\", \"level\": 0, \"level_question_nr\": 3, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" \", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \"1\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" formerly\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" known\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" as\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" D\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \"answer\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" is\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" an\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" AI\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" Assistant\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" that\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" connects\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" to\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" a\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" company's\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" documents\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" applications\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" personnel\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \".\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" It\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" provides\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" a\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" chat\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" interface\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" can\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" integrate\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" with\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" any\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" large\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" language\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" model\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" (\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \"LL\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \"M\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \")\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"top_documents\": [], \"rephrased_query\": \"What is Onyx 2?\", \"predicted_flow\": \"question-answer\", \"predicted_search\": \"keyword\", \"applied_source_filters\": null, \"applied_time_cutoff\": null, \"recency_bias_multiplier\": 0.5}\n" +"{\"llm_selected_doc_indices\": []}\n" +"{\"final_context_docs\": []}\n" +"{\"answer_piece\": \" of\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" choice\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \".\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" On\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \"yx\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" is\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" designed\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" to\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" be\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" modular\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" easily\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" extens\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \"ible\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" allowing\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" for\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" deployment\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" on\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" various\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" platforms\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" including\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" laptops\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" on\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \"-prem\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \"ise\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" or\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" cloud\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" environments\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \".\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" It\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" ensures\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" that\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" user\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" data\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" chats\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" remain\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \"I\", \"level\": 0, \"level_question_nr\": 1, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" under\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" don't\", \"level\": 0, \"level_question_nr\": 1, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" the\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" user's\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" know\", \"level\": 0, \"level_question_nr\": 1, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \".\", \"level\": 0, \"level_question_nr\": 1, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" control\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" as\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" the\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" deployment\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" is\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" owned\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" by\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" the\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" user\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \".\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" On\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \"yx\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" is\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" MIT\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" licensed\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" comes\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" ready\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" for\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" production\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" use\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" featuring\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" user\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" authentication\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" role\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" management\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" chat\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" persistence\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" a\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" user\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" interface\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" for\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" configuring\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" AI\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" Assist\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \"ants\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" their\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" prompts\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \".\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" Additionally\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" On\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \"yx\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" serves\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" as\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" a\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" unified\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" search\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" tool\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" across\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" common\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" workplace\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" applications\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" like\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" Slack\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" Google\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" Drive\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" Con\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \"fluence\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" enabling\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" it\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" to\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" act\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" as\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" a\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" subject\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" matter\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" expert\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" for\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" teams\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" by\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" combining\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" L\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \"LM\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \"s\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" with\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" team\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \"-specific\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" knowledge\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" [[1]]()\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"top_documents\": [], \"rephrased_query\": \"What is Onyx 3?\", \"predicted_flow\": \"question-answer\", \"predicted_search\": \"keyword\", \"applied_source_filters\": null, \"applied_time_cutoff\": null, \"recency_bias_multiplier\": 0.5}\n" +"{\"llm_selected_doc_indices\": []}\n" +"{\"final_context_docs\": []}\n" +"{\"answer_piece\": \"I\", \"level\": 0, \"level_question_nr\": 2, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" don't\", \"level\": 0, \"level_question_nr\": 2, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \" know\", \"level\": 0, \"level_question_nr\": 2, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"answer_piece\": \".\", \"level\": 0, \"level_question_nr\": 2, \"answer_type\": \"agent_sub_answer\"}\n" +"{\"top_documents\": [{\"document_id\": \"https://docs.onyx.app/introduction\", \"chunk_ind\": 0, \"semantic_identifier\": \"Introduction - Onyx Documentation\", \"link\": \"https://docs.onyx.app/introduction\", \"blurb\": \"Onyx Documentation home page\\nSearch...\\nNavigation\\nWelcome to Onyx\\nIntroduction\\nWelcome to Onyx\\nIntroduction\\nOnyx Overview\\n\\nWhat is Onyx\\nOnyx (Formerly Danswer) is the AI Assistant connected to your companys docs, apps, and people. Onyx provides a Chat interface and plugs into any LLM of your choice. Onyx can be deployed anywhere and for any scale - on a laptop, on-premise, or to cloud. Since you own the deployment, your user data and chats are fully in your own control. Onyx is MIT licensed and designed to be modular and easily extensible.\", \"source_type\": \"web\", \"boost\": 0, \"hidden\": false, \"metadata\": {}, \"score\": 0.6275177643886491, \"is_relevant\": null, \"relevance_explanation\": null, \"match_highlights\": [\"\", \"such as A customer wants feature X, is this already supported? or Wheres the pull request for feature Y?\\nOnyx can also be plugged into existing tools like Slack to get answers and AI chats directly in Slack.\\n\\nDemo\\n\\nMain Features \\n- Chat UI with the ability to select documents to chat with.\\n- Create custom AI Assistants\", \"\"], \"updated_at\": null, \"primary_owners\": null, \"secondary_owners\": null, \"is_internet\": false, \"db_doc_id\": 35923}], \"rephrased_query\": \"what is onyx 1, 2, 3, 4\", \"predicted_flow\": \"question-answer\", \"predicted_search\": \"keyword\", \"applied_source_filters\": null, \"applied_time_cutoff\": null, \"recency_bias_multiplier\": 0.5}\n" +"{\"llm_selected_doc_indices\": []}\n" +"{\"final_context_docs\": [{\"document_id\": \"https://docs.onyx.app/introduction\", \"content\": \"Onyx Documentation home page\\nSearch...\\nNavigation\\nWelcome to Onyx\\nIntroduction\\nWelcome to Onyx\\nIntroduction\\nOnyx Overview\\n\\nWhat is Onyx\\nOnyx (Formerly Danswer) is the AI Assistant connected to your companys docs, apps, and people. Onyx provides a Chat interface and plugs into any LLM of your choice. Onyx can be deployed anywhere and for any scale - on a laptop, on-premise, or to cloud. Since you own the deployment, your user data and chats are fully in your own control. Onyx is MIT licensed and designed to be modular and easily extensible. The system also comes fully ready for production usage with user authentication, role management (admin/basic users), chat persistence, and a UI for configuring Personas (AI Assistants) and their Prompts.\\nOnyx also serves as a Unified Search across all common workplace tools such as Slack, Google Drive, Confluence, etc. By combining LLMs and team specific knowledge, Onyx becomes a subject matter expert for the team. Its like ChatGPT if it had access to your teams unique knowledge! It enables questions such as A customer wants feature X, is this already supported? or Wheres the pull request for feature Y?\\nOnyx can also be plugged into existing tools like Slack to get answers and AI chats directly in Slack.\\n\\nDemo\\n\\nMain Features \\n- Chat UI with the ability to select documents to chat with.\\n- Create custom AI Assistants with different prompts and backing knowledge sets.\\n- Connect Onyx with LLM of your choice (self-host for a fully airgapped solution).\\n- Document Search + AI Answers for natural language queries.\\n- Connectors to all common workplace tools like Google Drive, Confluence, Slack, etc.\\n- Slack integration to get answers and search results directly in Slack.\\n\\nUpcoming\\n- Chat/Prompt sharing with specific teammates and user groups.\\n- Multi-modal model support, chat with images, video etc.\\n- Choosing between LLMs and parameters during chat session.\\n- Tool calling and agent configurations options.\\n- Organizational understanding and ability to locate and suggest experts from your team.\\n\\nOther Noteable Benefits of Onyx\\n- User Authentication with document level access management.\\n- Best in class Hybrid Search across all sources (BM-25 + prefix aware embedding models).\\n- Admin Dashboard to configure connectors, document-sets, access, etc.\\n- Custom deep learning models + learn from user feedback.\\n- Easy deployment and ability to host Onyx anywhere of your choosing.\\nQuickstart\", \"blurb\": \"Onyx Documentation home page\\nSearch...\\nNavigation\\nWelcome to Onyx\\nIntroduction\\nWelcome to Onyx\\nIntroduction\\nOnyx Overview\\n\\nWhat is Onyx\\nOnyx (Formerly Danswer) is the AI Assistant connected to your companys docs, apps, and people. Onyx provides a Chat interface and plugs into any LLM of your choice. Onyx can be deployed anywhere and for any scale - on a laptop, on-premise, or to cloud. Since you own the deployment, your user data and chats are fully in your own control. Onyx is MIT licensed and designed to be modular and easily extensible.\", \"semantic_identifier\": \"Introduction - Onyx Documentation\", \"source_type\": \"web\", \"metadata\": {}, \"updated_at\": null, \"link\": \"https://docs.onyx.app/introduction\", \"source_links\": {\"0\": \"https://docs.onyx.app/introduction\"}, \"match_highlights\": [\"\", \"such as A customer wants feature X, is this already supported? or Wheres the pull request for feature Y?\\nOnyx can also be plugged into existing tools like Slack to get answers and AI chats directly in Slack.\\n\\nDemo\\n\\nMain Features \\n- Chat UI with the ability to select documents to chat with.\\n- Create custom AI Assistants\", \"\"]}]}\n" +"{\"answer_piece\": \"I\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" cannot\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" reliably\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" answer\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" the\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" question\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" about\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" On\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"yx\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" \", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"2\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" \", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"3\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" \", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"4\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" as\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" the\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" provided\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" information\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" only\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" describes\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" On\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"yx\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" \", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"1\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" which\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" is\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" an\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" AI\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" Assistant\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" formerly\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" known\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" as\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" D\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"answer\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \".\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" On\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"yx\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" \", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"1\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" connects\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" to\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" a\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" company's\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" documents\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" applications\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" personnel\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" providing\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" a\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" chat\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" interface\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" integration\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" with\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" any\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" large\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" language\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" model\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" (\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"LL\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"M\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \")\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" of\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" choice\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \".\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" It\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" is\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" designed\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" to\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" be\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" modular\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" easily\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" extens\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"ible\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" can\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" be\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" deployed\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" on\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" various\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" platforms\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" while\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" ensuring\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" user\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" data\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" control\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \".\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" It\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" also\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" serves\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" as\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" a\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" unified\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" search\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" tool\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" across\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" common\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" workplace\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" applications\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" like\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" Slack\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" Google\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" Drive\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" and\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" Con\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"fluence\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" acting\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" as\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" a\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" subject\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" matter\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" expert\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" for\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" teams\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" [[1]]()\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"{{1}}\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"There\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" is\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" no\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" information\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" available\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" regarding\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" On\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"yx\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" \", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"2\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" \", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"3\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" or\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" \", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \"4\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \",\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" so\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" I\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" cannot\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" provide\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" details\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" about\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \" them\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"answer_piece\": \".\", \"level\": 0, \"level_question_nr\": 0, \"answer_type\": \"agent_level_answer\"}\n" +"{\"citations\": []}\n" +"{\"message_id\": 476, \"parent_message\": 475, \"latest_child_message\": null, \"message\": \"I cannot reliably answer the question about Onyx 2, 3, and 4, as the provided information only describes Onyx 1, which is an AI Assistant formerly known as Danswer. Onyx 1 connects to a company's documents, applications, and personnel, providing a chat interface and integration with any large language model (LLM) of choice. It is designed to be modular, easily extensible, and can be deployed on various platforms while ensuring user data control. It also serves as a unified search tool across common workplace applications like Slack, Google Drive, and Confluence, acting as a subject matter expert for teams [[1]](){{1}}There is no information available regarding Onyx 2, 3, or 4, so I cannot provide details about them.\", \"rephrased_query\": \"what is onyx 1, 2, 3, 4\", \"context_docs\": {\"top_documents\": [{\"document_id\": \"https://docs.onyx.app/introduction\", \"chunk_ind\": 0, \"semantic_identifier\": \"Introduction - Onyx Documentation\", \"link\": \"https://docs.onyx.app/introduction\", \"blurb\": \"Onyx Documentation home page\\nSearch...\\nNavigation\\nWelcome to Onyx\\nIntroduction\\nWelcome to Onyx\\nIntroduction\\nOnyx Overview\\n\\nWhat is Onyx\\nOnyx (Formerly Danswer) is the AI Assistant connected to your companys docs, apps, and people. Onyx provides a Chat interface and plugs into any LLM of your choice. Onyx can be deployed anywhere and for any scale - on a laptop, on-premise, or to cloud. Since you own the deployment, your user data and chats are fully in your own control. Onyx is MIT licensed and designed to be modular and easily extensible.\", \"source_type\": \"web\", \"boost\": 0, \"hidden\": false, \"metadata\": {}, \"score\": 0.6275177643886491, \"is_relevant\": null, \"relevance_explanation\": null, \"match_highlights\": [\"\", \"such as A customer wants feature X, is this already supported? or Wheres the pull request for feature Y?\\nOnyx can also be plugged into existing tools like Slack to get answers and AI chats directly in Slack.\\n\\nDemo\\n\\nMain Features \\n- Chat UI with the ability to select documents to chat with.\\n- Create custom AI Assistants\", \"\"], \"updated_at\": null, \"primary_owners\": null, \"secondary_owners\": null, \"is_internet\": false, \"db_doc_id\": 35923}]}, \"message_type\": \"assistant\", \"time_sent\": \"2025-01-12T05:37:18.318251+00:00\", \"overridden_model\": \"gpt-4o\", \"alternate_assistant_id\": 0, \"chat_session_id\": \"40f91916-7419-48d1-9681-5882b0869d88\", \"citations\": {}, \"sub_questions\": [], \"files\": [], \"tool_call\": null}\n" diff --git a/backend/onyx/auth/noauth_user.py b/backend/onyx/auth/noauth_user.py index e17694894e0..9a40e0cae49 100644 --- a/backend/onyx/auth/noauth_user.py +++ b/backend/onyx/auth/noauth_user.py @@ -23,6 +23,7 @@ def load_no_auth_user_preferences(store: KeyValueStore) -> UserPreferences: preferences_data = cast( Mapping[str, Any], store.load(KV_NO_AUTH_USER_PREFERENCES_KEY) ) + print("preferences_data", preferences_data) return UserPreferences(**preferences_data) except KvKeyNotFoundError: return UserPreferences( diff --git a/backend/onyx/configs/chat_configs.py b/backend/onyx/configs/chat_configs.py index 1560a280546..d99261294b0 100644 --- a/backend/onyx/configs/chat_configs.py +++ b/backend/onyx/configs/chat_configs.py @@ -1,6 +1,6 @@ import os - +INPUT_PROMPT_YAML = "./onyx/seeding/input_prompts.yaml" PROMPTS_YAML = "./onyx/seeding/prompts.yaml" PERSONAS_YAML = "./onyx/seeding/personas.yaml" diff --git a/backend/onyx/db/input_prompt.py b/backend/onyx/db/input_prompt.py new file mode 100644 index 00000000000..4437f825a53 --- /dev/null +++ b/backend/onyx/db/input_prompt.py @@ -0,0 +1,262 @@ +from uuid import UUID + +from fastapi import HTTPException +from sqlalchemy import or_ +from sqlalchemy import select +from sqlalchemy.orm import aliased +from sqlalchemy.orm import Session + +from onyx.configs.app_configs import AUTH_TYPE +from onyx.configs.constants import AuthType +from onyx.db.models import InputPrompt +from onyx.db.models import InputPrompt__User +from onyx.db.models import User +from onyx.server.features.input_prompt.models import InputPromptSnapshot +from onyx.server.manage.models import UserInfo +from onyx.utils.logger import setup_logger + +logger = setup_logger() + + +def insert_input_prompt_if_not_exists( + user: User | None, + input_prompt_id: int | None, + prompt: str, + content: str, + active: bool, + is_public: bool, + db_session: Session, + commit: bool = True, +) -> InputPrompt: + if input_prompt_id is not None: + input_prompt = ( + db_session.query(InputPrompt).filter_by(id=input_prompt_id).first() + ) + else: + query = db_session.query(InputPrompt).filter(InputPrompt.prompt == prompt) + if user: + query = query.filter(InputPrompt.user_id == user.id) + else: + query = query.filter(InputPrompt.user_id.is_(None)) + input_prompt = query.first() + + if input_prompt is None: + input_prompt = InputPrompt( + id=input_prompt_id, + prompt=prompt, + content=content, + active=active, + is_public=is_public or user is None, + user_id=user.id if user else None, + ) + db_session.add(input_prompt) + + if commit: + db_session.commit() + + return input_prompt + + +def insert_input_prompt( + prompt: str, + content: str, + is_public: bool, + user: User | None, + db_session: Session, +) -> InputPrompt: + input_prompt = InputPrompt( + prompt=prompt, + content=content, + active=True, + is_public=is_public, + user_id=user.id if user is not None else None, + ) + db_session.add(input_prompt) + db_session.commit() + + return input_prompt + + +def update_input_prompt( + user: User | None, + input_prompt_id: int, + prompt: str, + content: str, + active: bool, + db_session: Session, +) -> InputPrompt: + input_prompt = db_session.scalar( + select(InputPrompt).where(InputPrompt.id == input_prompt_id) + ) + if input_prompt is None: + raise ValueError(f"No input prompt with id {input_prompt_id}") + + if not validate_user_prompt_authorization(user, input_prompt): + raise HTTPException(status_code=401, detail="You don't own this prompt") + + input_prompt.prompt = prompt + input_prompt.content = content + input_prompt.active = active + + db_session.commit() + return input_prompt + + +def validate_user_prompt_authorization( + user: User | None, input_prompt: InputPrompt +) -> bool: + prompt = InputPromptSnapshot.from_model(input_prompt=input_prompt) + + if prompt.user_id is not None: + if user is None: + return False + + user_details = UserInfo.from_model(user) + if str(user_details.id) != str(prompt.user_id): + return False + return True + + +def remove_public_input_prompt(input_prompt_id: int, db_session: Session) -> None: + input_prompt = db_session.scalar( + select(InputPrompt).where(InputPrompt.id == input_prompt_id) + ) + + if input_prompt is None: + raise ValueError(f"No input prompt with id {input_prompt_id}") + + if not input_prompt.is_public: + raise HTTPException(status_code=400, detail="This prompt is not public") + + db_session.delete(input_prompt) + db_session.commit() + + +def remove_input_prompt( + user: User | None, + input_prompt_id: int, + db_session: Session, + delete_public: bool = False, +) -> None: + input_prompt = db_session.scalar( + select(InputPrompt).where(InputPrompt.id == input_prompt_id) + ) + if input_prompt is None: + raise ValueError(f"No input prompt with id {input_prompt_id}") + + if input_prompt.is_public and not delete_public: + raise HTTPException( + status_code=400, detail="Cannot delete public prompts with this method" + ) + + if not validate_user_prompt_authorization(user, input_prompt): + raise HTTPException(status_code=401, detail="You do not own this prompt") + + db_session.delete(input_prompt) + db_session.commit() + + +def fetch_input_prompt_by_id( + id: int, user_id: UUID | None, db_session: Session +) -> InputPrompt: + query = select(InputPrompt).where(InputPrompt.id == id) + + if user_id: + query = query.where( + (InputPrompt.user_id == user_id) | (InputPrompt.user_id is None) + ) + else: + # If no user_id is provided, only fetch prompts without a user_id (aka public) + query = query.where(InputPrompt.user_id == None) # noqa + + result = db_session.scalar(query) + + if result is None: + raise HTTPException(422, "No input prompt found") + + return result + + +def fetch_public_input_prompts( + db_session: Session, +) -> list[InputPrompt]: + query = select(InputPrompt).where(InputPrompt.is_public) + return list(db_session.scalars(query).all()) + + +def fetch_input_prompts_by_user( + db_session: Session, + user_id: UUID | None, + active: bool | None = None, + include_public: bool = False, +) -> list[InputPrompt]: + """ + Returns all prompts belonging to the user or public prompts, + excluding those the user has specifically disabled. + """ + + # Start with a basic query for InputPrompt + query = select(InputPrompt) + + # If we have a user, left join to InputPrompt__User so we can check "disabled" + if user_id is not None: + IPU = aliased(InputPrompt__User) + query = query.join( + IPU, + (IPU.input_prompt_id == InputPrompt.id) & (IPU.user_id == user_id), + isouter=True, + ) + + # Exclude disabled prompts + # i.e. keep only those where (IPU.disabled is NULL or False) + query = query.where(or_(IPU.disabled.is_(None), IPU.disabled.is_(False))) + + if include_public: + # user-owned or public + query = query.where( + (InputPrompt.user_id == user_id) | (InputPrompt.is_public) + ) + else: + # only user-owned prompts + query = query.where(InputPrompt.user_id == user_id) + + # If no user is logged in, get all prompts (public and private) + if user_id is None and AUTH_TYPE == AuthType.DISABLED: + query = query.where(True) # type: ignore + + # If no user is logged in but we want to include public prompts + elif include_public: + query = query.where(InputPrompt.is_public) + + if active is not None: + query = query.where(InputPrompt.active == active) + + return list(db_session.scalars(query).all()) + + +def disable_input_prompt_for_user( + input_prompt_id: int, + user_id: UUID, + db_session: Session, +) -> None: + """ + Sets (or creates) a record in InputPrompt__User with disabled=True + so that this prompt is hidden for the user. + """ + ipu = ( + db_session.query(InputPrompt__User) + .filter_by(input_prompt_id=input_prompt_id, user_id=user_id) + .first() + ) + + if ipu is None: + # Create a new association row + ipu = InputPrompt__User( + input_prompt_id=input_prompt_id, user_id=user_id, disabled=True + ) + db_session.add(ipu) + else: + # Just update the existing record + ipu.disabled = True + + db_session.commit() diff --git a/backend/onyx/db/models.py b/backend/onyx/db/models.py index 241c137cd5c..94500fb9867 100644 --- a/backend/onyx/db/models.py +++ b/backend/onyx/db/models.py @@ -151,6 +151,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) + shortcut_enabled: Mapped[bool] = mapped_column(Boolean, default=True) chosen_assistants: Mapped[list[int] | None] = mapped_column( postgresql.JSONB(), nullable=True, default=None ) @@ -163,6 +164,9 @@ class User(SQLAlchemyBaseUserTableUUID, Base): recent_assistants: Mapped[list[dict]] = mapped_column( postgresql.JSONB(), nullable=False, default=list, server_default="[]" ) + pinned_assistants: Mapped[list[int] | None] = mapped_column( + postgresql.JSONB(), nullable=True, default=None + ) oidc_expiry: Mapped[datetime.datetime] = mapped_column( TIMESTAMPAware(timezone=True), nullable=True @@ -184,7 +188,9 @@ class User(SQLAlchemyBaseUserTableUUID, Base): ) prompts: Mapped[list["Prompt"]] = relationship("Prompt", back_populates="user") - + input_prompts: Mapped[list["InputPrompt"]] = relationship( + "InputPrompt", back_populates="user" + ) # Personas owned by this user personas: Mapped[list["Persona"]] = relationship("Persona", back_populates="user") # Custom tools created by this user @@ -1429,8 +1435,17 @@ class StarterMessage(TypedDict): class StarterMessageModel(BaseModel): - name: str message: str + name: str + + +class Persona__PersonaLabel(Base): + __tablename__ = "persona__persona_label" + + persona_id: Mapped[int] = mapped_column(ForeignKey("persona.id"), primary_key=True) + persona_label_id: Mapped[int] = mapped_column( + ForeignKey("persona_label.id", ondelete="CASCADE"), primary_key=True + ) class Persona(Base): @@ -1455,9 +1470,7 @@ class Persona(Base): recency_bias: Mapped[RecencyBiasSetting] = mapped_column( Enum(RecencyBiasSetting, native_enum=False) ) - category_id: Mapped[int | None] = mapped_column( - ForeignKey("persona_category.id"), nullable=True - ) + # Allows the Persona to specify a different LLM version than is controlled # globablly via env variables. For flexibility, validity is not currently enforced # NOTE: only is applied on the actual response generation - is not used for things like @@ -1529,10 +1542,11 @@ class Persona(Base): secondary="persona__user_group", viewonly=True, ) - category: Mapped["PersonaCategory"] = relationship( - "PersonaCategory", back_populates="personas" + labels: Mapped[list["PersonaLabel"]] = relationship( + "PersonaLabel", + secondary=Persona__PersonaLabel.__table__, + back_populates="personas", ) - # Default personas loaded via yaml cannot have the same name __table_args__ = ( Index( @@ -1544,14 +1558,17 @@ class Persona(Base): ) -class PersonaCategory(Base): - __tablename__ = "persona_category" +class PersonaLabel(Base): + __tablename__ = "persona_label" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String, unique=True) - description: Mapped[str | None] = mapped_column(String, nullable=True) personas: Mapped[list["Persona"]] = relationship( - "Persona", back_populates="category" + "Persona", + secondary=Persona__PersonaLabel.__table__, + back_populates="labels", + cascade="all, delete-orphan", + single_parent=True, ) @@ -1974,6 +1991,32 @@ class UsageReport(Base): file = relationship("PGFileStore") +class InputPrompt(Base): + __tablename__ = "inputprompt" + + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + prompt: Mapped[str] = mapped_column(String) + content: Mapped[str] = mapped_column(String) + active: Mapped[bool] = mapped_column(Boolean) + user: Mapped[User | None] = relationship("User", back_populates="input_prompts") + is_public: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) + user_id: Mapped[UUID | None] = mapped_column( + ForeignKey("user.id", ondelete="CASCADE"), nullable=True + ) + + +class InputPrompt__User(Base): + __tablename__ = "inputprompt__user" + + input_prompt_id: Mapped[int] = mapped_column( + ForeignKey("inputprompt.id"), primary_key=True + ) + user_id: Mapped[UUID | None] = mapped_column( + ForeignKey("inputprompt.id"), primary_key=True + ) + disabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False) + + """ Multi-tenancy related tables """ diff --git a/backend/onyx/db/persona.py b/backend/onyx/db/persona.py index 6da756eba10..de277c32f86 100644 --- a/backend/onyx/db/persona.py +++ b/backend/onyx/db/persona.py @@ -28,7 +28,7 @@ from onyx.db.models import DocumentSet from onyx.db.models import Persona from onyx.db.models import Persona__User from onyx.db.models import Persona__UserGroup -from onyx.db.models import PersonaCategory +from onyx.db.models import PersonaLabel from onyx.db.models import Prompt from onyx.db.models import StarterMessage from onyx.db.models import Tool @@ -460,7 +460,7 @@ def upsert_persona( search_start_date: datetime | None = None, builtin_persona: bool = False, is_default_persona: bool = False, - category_id: int | None = None, + label_ids: list[int] | None = None, chunks_above: int = CONTEXT_CHUNKS_ABOVE, chunks_below: int = CONTEXT_CHUNKS_BELOW, ) -> Persona: @@ -506,6 +506,12 @@ def upsert_persona( f"specified. Specified IDs were: '{prompt_ids}'" ) + labels = None + if label_ids is not None: + labels = ( + db_session.query(PersonaLabel).filter(PersonaLabel.id.in_(label_ids)).all() + ) + # ensure all specified tools are valid if tools: validate_persona_tools(tools) @@ -547,7 +553,7 @@ def upsert_persona( existing_persona.uploaded_image_id = uploaded_image_id existing_persona.is_visible = is_visible existing_persona.search_start_date = search_start_date - existing_persona.category_id = category_id + existing_persona.labels = labels or [] # Do not delete any associations manually added unless # a new updated list is provided if document_sets is not None: @@ -600,7 +606,7 @@ def upsert_persona( is_visible=is_visible, search_start_date=search_start_date, is_default_persona=is_default_persona, - category_id=category_id, + labels=labels or [], ) db_session.add(new_persona) persona = new_persona @@ -821,37 +827,31 @@ def delete_persona_by_name( db_session.commit() -def get_assistant_categories(db_session: Session) -> list[PersonaCategory]: - return db_session.query(PersonaCategory).all() +def get_assistant_labels(db_session: Session) -> list[PersonaLabel]: + return db_session.query(PersonaLabel).all() -def create_assistant_category( - db_session: Session, name: str, description: str -) -> PersonaCategory: - category = PersonaCategory(name=name, description=description) - db_session.add(category) +def create_assistant_label(db_session: Session, name: str) -> PersonaLabel: + label = PersonaLabel(name=name) + db_session.add(label) db_session.commit() - return category + return label -def update_persona_category( - category_id: int, - category_description: str, - category_name: str, +def update_persona_label( + label_id: int, + label_name: str, db_session: Session, ) -> None: - persona_category = ( - db_session.query(PersonaCategory) - .filter(PersonaCategory.id == category_id) - .one_or_none() + persona_label = ( + db_session.query(PersonaLabel).filter(PersonaLabel.id == label_id).one_or_none() ) - if persona_category is None: - raise ValueError(f"Persona category with ID {category_id} does not exist") - persona_category.description = category_description - persona_category.name = category_name + if persona_label is None: + raise ValueError(f"Persona label with ID {label_id} does not exist") + persona_label.name = label_name db_session.commit() -def delete_persona_category(category_id: int, db_session: Session) -> None: - db_session.query(PersonaCategory).filter(PersonaCategory.id == category_id).delete() +def delete_persona_label(label_id: int, db_session: Session) -> None: + db_session.query(PersonaLabel).filter(PersonaLabel.id == label_id).delete() db_session.commit() diff --git a/backend/onyx/main.py b/backend/onyx/main.py index 648a4be9144..382d101b845 100644 --- a/backend/onyx/main.py +++ b/backend/onyx/main.py @@ -55,6 +55,12 @@ from onyx.server.documents.indexing import router as indexing_router from onyx.server.documents.standard_oauth import router as oauth_router from onyx.server.features.document_set.api import router as document_set_router from onyx.server.features.folder.api import router as folder_router +from onyx.server.features.input_prompt.api import ( + admin_router as admin_input_prompt_router, +) +from onyx.server.features.input_prompt.api import ( + basic_router as input_prompt_router, +) from onyx.server.features.notifications.api import router as notification_router from onyx.server.features.persona.api import admin_router as admin_persona_router from onyx.server.features.persona.api import basic_router as persona_router @@ -278,6 +284,8 @@ def get_application() -> FastAPI: include_router_with_global_prefix_prepended(application, connector_router) include_router_with_global_prefix_prepended(application, user_router) include_router_with_global_prefix_prepended(application, credential_router) + include_router_with_global_prefix_prepended(application, input_prompt_router) + include_router_with_global_prefix_prepended(application, admin_input_prompt_router) include_router_with_global_prefix_prepended(application, cc_pair_router) include_router_with_global_prefix_prepended(application, folder_router) include_router_with_global_prefix_prepended(application, document_set_router) diff --git a/backend/onyx/prompts/starter_messages.py b/backend/onyx/prompts/starter_messages.py index 3ef8066e980..b54a670873a 100644 --- a/backend/onyx/prompts/starter_messages.py +++ b/backend/onyx/prompts/starter_messages.py @@ -1,9 +1,10 @@ PERSONA_CATEGORY_GENERATION_PROMPT = """ -Based on the assistant's name, description, and instructions, generate a list of {num_categories} +Based on the assistant's name, description, and instructions, generate {num_categories} **unique and diverse** categories that represent different types of starter messages a user might send to initiate a conversation with this chatbot assistant. -**Ensure that the categories are varied and cover a wide range of topics related to the assistant's capabilities.** +**Ensure that the categories are relevant and cover +topics related to the assistant's capabilities.** Provide the categories as a JSON array of strings **without any code fences or additional text**. @@ -11,27 +12,20 @@ Provide the categories as a JSON array of strings **without any code fences or a - **Name**: {name} - **Description**: {description} - **Instructions**: {instructions} -""".strip() +""" PERSONA_STARTER_MESSAGE_CREATION_PROMPT = """ Create a starter message that a **user** might send to initiate a conversation with a chatbot assistant. -**Category**: {category} +{category_prompt} -Your response should include two parts: - -1. **Title**: A short, engaging title that reflects the user's intent - (e.g., 'Need Travel Advice', 'Question About Coding', 'Looking for Book Recommendations'). - -2. **Message**: The actual message that the user would send to the assistant. - This should be natural, engaging, and encourage a helpful response from the assistant. - **Avoid overly specific details; keep the message general and broadly applicable.** +Your response should only include the actual message that the user would send to the assistant. +This should be natural, engaging, and encourage a helpful response from the assistant. +**Avoid overly specific details; keep the message general and broadly applicable.** For example: - Instead of "I've just adopted a 6-month-old Labrador puppy who's pulling on the leash," write "I'm having trouble training my new puppy to walk nicely on a leash." - -Ensure each part is clearly labeled and separated as shown above. Do not provide any additional text or explanation and be extremely concise **Context about the assistant:** @@ -41,6 +35,18 @@ Do not provide any additional text or explanation and be extremely concise """.strip() +def format_persona_starter_message_prompt( + name: str, description: str, instructions: str, category: str | None = None +) -> str: + category_prompt = f"**Category**: {category}" if category else "" + return PERSONA_STARTER_MESSAGE_CREATION_PROMPT.format( + category_prompt=category_prompt, + name=name, + description=description, + instructions=instructions, + ) + + if __name__ == "__main__": print(PERSONA_CATEGORY_GENERATION_PROMPT) print(PERSONA_STARTER_MESSAGE_CREATION_PROMPT) diff --git a/backend/onyx/secondary_llm_flows/starter_message_creation.py b/backend/onyx/secondary_llm_flows/starter_message_creation.py index 464c9610ce9..7fda3255c3c 100644 --- a/backend/onyx/secondary_llm_flows/starter_message_creation.py +++ b/backend/onyx/secondary_llm_flows/starter_message_creation.py @@ -1,15 +1,12 @@ import json -import re from typing import Any from typing import cast -from typing import Dict from typing import List from litellm import get_supported_openai_params from sqlalchemy.orm import Session from onyx.configs.chat_configs import NUM_PERSONA_PROMPT_GENERATION_CHUNKS -from onyx.configs.chat_configs import NUM_PERSONA_PROMPTS from onyx.context.search.models import IndexFilters from onyx.context.search.models import InferenceChunk from onyx.context.search.postprocessing.postprocessing import cleanup_chunks @@ -22,8 +19,8 @@ from onyx.db.models import User from onyx.document_index.document_index_utils import get_both_index_names from onyx.document_index.factory import get_default_document_index from onyx.llm.factory import get_default_llms +from onyx.prompts.starter_messages import format_persona_starter_message_prompt from onyx.prompts.starter_messages import PERSONA_CATEGORY_GENERATION_PROMPT -from onyx.prompts.starter_messages import PERSONA_STARTER_MESSAGE_CREATION_PROMPT from onyx.utils.logger import setup_logger from onyx.utils.threadpool_concurrency import FunctionCall from onyx.utils.threadpool_concurrency import run_functions_in_parallel @@ -49,7 +46,7 @@ def get_random_chunks_from_doc_sets( return cleanup_chunks(chunks) -def parse_categories(content: str) -> List[str]: +def parse_categories(content: str) -> List[str | None]: """ Parses the JSON array of categories from the LLM response. """ @@ -73,7 +70,7 @@ def generate_start_message_prompts( name: str, description: str, instructions: str, - categories: List[str], + categories: List[str | None], chunk_contents: str, supports_structured_output: bool, fast_llm: Any, @@ -84,13 +81,11 @@ def generate_start_message_prompts( functions = [] for category in categories: # Create a prompt specific to the category - start_message_generation_prompt = ( - PERSONA_STARTER_MESSAGE_CREATION_PROMPT.format( - name=name, - description=description, - instructions=instructions, - category=category, - ) + start_message_generation_prompt = format_persona_starter_message_prompt( + name=name, + description=description, + instructions=instructions, + category=category, ) if chunk_contents: @@ -101,89 +96,21 @@ def generate_start_message_prompts( "\n'''" ) - if supports_structured_output: - functions.append( - FunctionCall( - fast_llm.invoke, - (start_message_generation_prompt, None, None, StarterMessage), - ) - ) - else: - functions.append( - FunctionCall( - fast_llm.invoke, - (start_message_generation_prompt,), - ) + functions.append( + FunctionCall( + fast_llm.invoke, + (start_message_generation_prompt,), ) + ) return functions -def parse_unstructured_output(output: str) -> Dict[str, str]: - """ - Parses the assistant's unstructured output into a dictionary with keys: - - 'name' (Title) - - 'message' (Message) - """ - - # Debug output - logger.debug(f"LLM Output for starter message creation: {output}") - - # Patterns to match - title_pattern = r"(?i)^\**Title\**\s*:\s*(.+)" - message_pattern = r"(?i)^\**Message\**\s*:\s*(.+)" - - # Initialize the response dictionary - response_dict = {} - - # Split the output into lines - lines = output.strip().split("\n") - - # Variables to keep track of the current key being processed - current_key = None - current_value_lines = [] - - for line in lines: - # Check for title - title_match = re.match(title_pattern, line.strip()) - if title_match: - # Save previous key-value pair if any - if current_key and current_value_lines: - response_dict[current_key] = " ".join(current_value_lines).strip() - current_value_lines = [] - current_key = "name" - current_value_lines.append(title_match.group(1).strip()) - continue - - # Check for message - message_match = re.match(message_pattern, line.strip()) - if message_match: - if current_key and current_value_lines: - response_dict[current_key] = " ".join(current_value_lines).strip() - current_value_lines = [] - current_key = "message" - current_value_lines.append(message_match.group(1).strip()) - continue - - # If the line doesn't match a new key, append it to the current value - if current_key: - current_value_lines.append(line.strip()) - - # Add the last key-value pair - if current_key and current_value_lines: - response_dict[current_key] = " ".join(current_value_lines).strip() - - # Validate that the necessary keys are present - if not all(k in response_dict for k in ["name", "message"]): - raise ValueError("Failed to parse the assistant's response.") - - return response_dict - - def generate_starter_messages( name: str, description: str, instructions: str, document_set_ids: List[int], + generation_count: int, db_session: Session, user: User | None, ) -> List[StarterMessage]: @@ -201,20 +128,26 @@ def generate_starter_messages( isinstance(params, list) and "response_format" in params ) - # Generate categories - category_generation_prompt = PERSONA_CATEGORY_GENERATION_PROMPT.format( - name=name, - description=description, - instructions=instructions, - num_categories=NUM_PERSONA_PROMPTS, - ) + categories: list[str | None] = [] - category_response = fast_llm.invoke(category_generation_prompt) - categories = parse_categories(cast(str, category_response.content)) + if generation_count > 1: + # Generate categories + category_generation_prompt = PERSONA_CATEGORY_GENERATION_PROMPT.format( + name=name, + description=description, + instructions=instructions, + num_categories=generation_count, + ) - if not categories: - logger.error("No categories were generated.") - return [] + category_response = fast_llm.invoke(category_generation_prompt) + categories = parse_categories(cast(str, category_response.content)) + + if not categories: + logger.error("No categories were generated.") + return [] + + else: + categories = [None] # Fetch example content if document sets are provided if document_set_ids: @@ -254,18 +187,9 @@ def generate_starter_messages( prompts = [] for response in results.values(): - try: - if supports_structured_output: - response_dict = json.loads(response.content) - else: - response_dict = parse_unstructured_output(response.content) - starter_message = StarterMessage( - name=response_dict["name"], - message=response_dict["message"], - ) - prompts.append(starter_message) - except (json.JSONDecodeError, ValueError) as e: - logger.error(f"Failed to parse starter message: {e}") - continue + starter_message = StarterMessage( + message=response.content, name=response.content + ) + prompts.append(starter_message) return prompts diff --git a/backend/onyx/seeding/input_prompts.yaml b/backend/onyx/seeding/input_prompts.yaml new file mode 100644 index 00000000000..cc7dbe78ea1 --- /dev/null +++ b/backend/onyx/seeding/input_prompts.yaml @@ -0,0 +1,24 @@ +input_prompts: + - id: -5 + prompt: "Elaborate" + content: "Elaborate on the above, give me a more in depth explanation." + active: true + is_public: true + + - id: -4 + prompt: "Reword" + content: "Help me rewrite the following politely and concisely for professional communication:\n" + active: true + is_public: true + + - id: -3 + prompt: "Email" + content: "Write a professional email for me including a subject line, signature, etc. Template the parts that need editing with [ ]. The email should cover the following points:\n" + active: true + is_public: true + + - id: -2 + prompt: "Debug" + content: "Provide step-by-step troubleshooting instructions for the following issue:\n" + active: true + is_public: true diff --git a/backend/onyx/seeding/load_yamls.py b/backend/onyx/seeding/load_yamls.py index 5edf5d318c5..8d06ec181ac 100644 --- a/backend/onyx/seeding/load_yamls.py +++ b/backend/onyx/seeding/load_yamls.py @@ -1,11 +1,13 @@ import yaml from sqlalchemy.orm import Session +from onyx.configs.chat_configs import INPUT_PROMPT_YAML from onyx.configs.chat_configs import MAX_CHUNKS_FED_TO_CHAT from onyx.configs.chat_configs import PERSONAS_YAML from onyx.configs.chat_configs import PROMPTS_YAML from onyx.context.search.enums import RecencyBiasSetting from onyx.db.document_set import get_or_create_document_set_by_name +from onyx.db.input_prompt import insert_input_prompt_if_not_exists from onyx.db.models import DocumentSet as DocumentSetDBModel from onyx.db.models import Persona from onyx.db.models import Prompt as PromptDBModel @@ -39,6 +41,29 @@ def load_prompts_from_yaml( ) +def load_input_prompts_from_yaml( + db_session: Session, input_prompts_yaml: str = INPUT_PROMPT_YAML +) -> None: + with open(input_prompts_yaml, "r") as file: + data = yaml.safe_load(file) + + all_input_prompts = data.get("input_prompts", []) + for input_prompt in all_input_prompts: + # If these prompts are deleted (which is a hard delete in the DB), on server startup + # they will be recreated, but the user can always just deactivate them, just a light inconvenience + + insert_input_prompt_if_not_exists( + user=None, + input_prompt_id=input_prompt.get("id"), + prompt=input_prompt["prompt"], + content=input_prompt["content"], + is_public=input_prompt["is_public"], + active=input_prompt.get("active", True), + db_session=db_session, + commit=True, + ) + + def load_personas_from_yaml( db_session: Session, personas_yaml: str = PERSONAS_YAML, @@ -113,7 +138,7 @@ def load_personas_from_yaml( if persona.get("num_chunks") is not None else default_chunks, llm_relevance_filter=persona.get("llm_relevance_filter"), - starter_messages=persona.get("starter_messages"), + starter_messages=persona.get("starter_messages", []), llm_filter_extraction=persona.get("llm_filter_extraction"), icon_shape=persona.get("icon_shape"), icon_color=persona.get("icon_color"), @@ -144,6 +169,8 @@ def load_chat_yamls( db_session: Session, prompt_yaml: str = PROMPTS_YAML, personas_yaml: str = PERSONAS_YAML, + input_prompts_yaml: str = INPUT_PROMPT_YAML, ) -> None: load_prompts_from_yaml(db_session, prompt_yaml) load_personas_from_yaml(db_session, personas_yaml) + load_input_prompts_from_yaml(db_session, input_prompts_yaml) diff --git a/backend/onyx/server/documents/document.py b/backend/onyx/server/documents/document.py index c682aedbed5..c84a8288f66 100644 --- a/backend/onyx/server/documents/document.py +++ b/backend/onyx/server/documents/document.py @@ -90,6 +90,7 @@ def get_chunk_info( min_chunk_ind=chunk_id, max_chunk_ind=chunk_id, ) + inference_chunks = document_index.id_based_retrieval( chunk_requests=[chunk_request], filters=IndexFilters(access_control_list=user_acl_filters), diff --git a/backend/onyx/server/features/input_prompt/api.py b/backend/onyx/server/features/input_prompt/api.py new file mode 100644 index 00000000000..0506d933db9 --- /dev/null +++ b/backend/onyx/server/features/input_prompt/api.py @@ -0,0 +1,156 @@ +from fastapi import APIRouter +from fastapi import Depends +from fastapi import HTTPException +from sqlalchemy.orm import Session + +from onyx.auth.users import current_admin_user +from onyx.auth.users import current_user +from onyx.db.engine import get_session +from onyx.db.input_prompt import disable_input_prompt_for_user +from onyx.db.input_prompt import fetch_input_prompt_by_id +from onyx.db.input_prompt import fetch_input_prompts_by_user +from onyx.db.input_prompt import insert_input_prompt +from onyx.db.input_prompt import remove_input_prompt +from onyx.db.input_prompt import remove_public_input_prompt +from onyx.db.input_prompt import update_input_prompt +from onyx.db.models import InputPrompt__User +from onyx.db.models import User +from onyx.server.features.input_prompt.models import CreateInputPromptRequest +from onyx.server.features.input_prompt.models import InputPromptSnapshot +from onyx.server.features.input_prompt.models import UpdateInputPromptRequest +from onyx.utils.logger import setup_logger + +logger = setup_logger() + +basic_router = APIRouter(prefix="/input_prompt") +admin_router = APIRouter(prefix="/admin/input_prompt") + + +@basic_router.get("") +def list_input_prompts( + user: User | None = Depends(current_user), + include_public: bool = True, + db_session: Session = Depends(get_session), +) -> list[InputPromptSnapshot]: + user_prompts = fetch_input_prompts_by_user( + user_id=user.id if user is not None else None, + db_session=db_session, + include_public=include_public, + ) + return [InputPromptSnapshot.from_model(prompt) for prompt in user_prompts] + + +@basic_router.get("/{input_prompt_id}") +def get_input_prompt( + input_prompt_id: int, + user: User | None = Depends(current_user), + db_session: Session = Depends(get_session), +) -> InputPromptSnapshot: + input_prompt = fetch_input_prompt_by_id( + id=input_prompt_id, + user_id=user.id if user is not None else None, + db_session=db_session, + ) + + return InputPromptSnapshot.from_model(input_prompt=input_prompt) + + +@basic_router.post("") +def create_input_prompt( + create_input_prompt_request: CreateInputPromptRequest, + user: User | None = Depends(current_user), + db_session: Session = Depends(get_session), +) -> InputPromptSnapshot: + input_prompt = insert_input_prompt( + prompt=create_input_prompt_request.prompt, + content=create_input_prompt_request.content, + is_public=False, + user=user, + db_session=db_session, + ) + + if user is not None: + input_prompt_user = InputPrompt__User( + input_prompt_id=input_prompt.id, user_id=user.id + ) + db_session.add(input_prompt_user) + db_session.commit() + + return InputPromptSnapshot.from_model(input_prompt) + + +@basic_router.patch("/{input_prompt_id}") +def patch_input_prompt( + input_prompt_id: int, + update_input_prompt_request: UpdateInputPromptRequest, + user: User | None = Depends(current_user), + db_session: Session = Depends(get_session), +) -> InputPromptSnapshot: + try: + updated_input_prompt = update_input_prompt( + user=user, + input_prompt_id=input_prompt_id, + prompt=update_input_prompt_request.prompt, + content=update_input_prompt_request.content, + active=update_input_prompt_request.active, + db_session=db_session, + ) + except ValueError as e: + error_msg = "Error occurred while updated input prompt" + logger.warn(f"{error_msg}. Stack trace: {e}") + raise HTTPException(status_code=404, detail=error_msg) + + return InputPromptSnapshot.from_model(updated_input_prompt) + + +@basic_router.delete("/{input_prompt_id}") +def delete_input_prompt( + input_prompt_id: int, + user: User | None = Depends(current_user), + db_session: Session = Depends(get_session), + delete_public: bool = False, +) -> None: + try: + remove_input_prompt( + user, input_prompt_id, db_session, delete_public=delete_public + ) + + except ValueError as e: + error_msg = "Error occurred while deleting input prompt" + logger.warn(f"{error_msg}. Stack trace: {e}") + raise HTTPException(status_code=404, detail=error_msg) + + +@admin_router.delete("/{input_prompt_id}") +def delete_public_input_prompt( + input_prompt_id: int, + _: User | None = Depends(current_admin_user), + db_session: Session = Depends(get_session), +) -> None: + try: + remove_public_input_prompt(input_prompt_id, db_session) + + except ValueError as e: + error_msg = "Error occurred while deleting input prompt" + logger.warn(f"{error_msg}. Stack trace: {e}") + raise HTTPException(status_code=404, detail=error_msg) + + +@basic_router.post("/{input_prompt_id}/hide") +def hide_input_prompt_for_user( + input_prompt_id: int, + user: User | None = Depends(current_user), + db_session: Session = Depends(get_session), +) -> None: + """ + Endpoint that marks a seed (or any) prompt as disabled for the current user, + so it won't show up in their subsequent queries. + """ + if user is None: + # if auth is disabled, just delete the prompt + delete_input_prompt(input_prompt_id, user, db_session, delete_public=True) + + else: + disable_input_prompt_for_user(input_prompt_id, user.id, db_session) + + return None diff --git a/backend/onyx/server/features/input_prompt/models.py b/backend/onyx/server/features/input_prompt/models.py new file mode 100644 index 00000000000..27927799b0a --- /dev/null +++ b/backend/onyx/server/features/input_prompt/models.py @@ -0,0 +1,47 @@ +from uuid import UUID + +from pydantic import BaseModel + +from onyx.db.models import InputPrompt +from onyx.utils.logger import setup_logger + +logger = setup_logger() + + +class CreateInputPromptRequest(BaseModel): + prompt: str + content: str + is_public: bool + + +class UpdateInputPromptRequest(BaseModel): + prompt: str + content: str + active: bool + + +class InputPromptResponse(BaseModel): + id: int + prompt: str + content: str + active: bool + + +class InputPromptSnapshot(BaseModel): + id: int + prompt: str + content: str + active: bool + user_id: UUID | None + is_public: bool + + @classmethod + def from_model(cls, input_prompt: InputPrompt) -> "InputPromptSnapshot": + return InputPromptSnapshot( + id=input_prompt.id, + prompt=input_prompt.prompt, + content=input_prompt.content, + active=input_prompt.active, + user_id=input_prompt.user_id, + is_public=input_prompt.is_public, + ) diff --git a/backend/onyx/server/features/persona/api.py b/backend/onyx/server/features/persona/api.py index 0d4ecc4e2ee..a318ef7b5c6 100644 --- a/backend/onyx/server/features/persona/api.py +++ b/backend/onyx/server/features/persona/api.py @@ -23,16 +23,16 @@ from onyx.db.engine import get_session from onyx.db.models import StarterMessageModel as StarterMessage from onyx.db.models import User from onyx.db.notification import create_notification -from onyx.db.persona import create_assistant_category +from onyx.db.persona import create_assistant_label from onyx.db.persona import create_update_persona -from onyx.db.persona import delete_persona_category -from onyx.db.persona import get_assistant_categories +from onyx.db.persona import delete_persona_label +from onyx.db.persona import get_assistant_labels from onyx.db.persona import get_persona_by_id from onyx.db.persona import get_personas_for_user from onyx.db.persona import mark_persona_as_deleted from onyx.db.persona import mark_persona_as_not_deleted from onyx.db.persona import update_all_personas_display_priority -from onyx.db.persona import update_persona_category +from onyx.db.persona import update_persona_label from onyx.db.persona import update_persona_public_status from onyx.db.persona import update_persona_shared_users from onyx.db.persona import update_persona_visibility @@ -44,8 +44,8 @@ from onyx.secondary_llm_flows.starter_message_creation import ( from onyx.server.features.persona.models import CreatePersonaRequest from onyx.server.features.persona.models import GenerateStarterMessageRequest from onyx.server.features.persona.models import ImageGenerationToolStatus -from onyx.server.features.persona.models import PersonaCategoryCreate -from onyx.server.features.persona.models import PersonaCategoryResponse +from onyx.server.features.persona.models import PersonaLabelCreate +from onyx.server.features.persona.models import PersonaLabelResponse from onyx.server.features.persona.models import PersonaSharedNotificationData from onyx.server.features.persona.models import PersonaSnapshot from onyx.server.features.persona.models import PromptTemplateResponse @@ -214,57 +214,53 @@ def update_persona( ) -class PersonaCategoryPatchRequest(BaseModel): - category_description: str - category_name: str +class PersonaLabelPatchRequest(BaseModel): + label_name: str -@basic_router.get("/categories") -def get_categories( +@basic_router.get("/labels") +def get_labels( db: Session = Depends(get_session), _: User | None = Depends(current_user), -) -> list[PersonaCategoryResponse]: +) -> list[PersonaLabelResponse]: return [ - PersonaCategoryResponse.from_model(category) - for category in get_assistant_categories(db_session=db) + PersonaLabelResponse.from_model(label) + for label in get_assistant_labels(db_session=db) ] -@admin_router.post("/categories") -def create_category( - category: PersonaCategoryCreate, +@basic_router.post("/labels") +def create_label( + label: PersonaLabelCreate, db: Session = Depends(get_session), - _: User | None = Depends(current_admin_user), -) -> PersonaCategoryResponse: - """Create a new assistant category""" - category_model = create_assistant_category( - name=category.name, description=category.description, db_session=db - ) - return PersonaCategoryResponse.from_model(category_model) + _: User | None = Depends(current_user), +) -> PersonaLabelResponse: + """Create a new assistant label""" + label_model = create_assistant_label(name=label.name, db_session=db) + return PersonaLabelResponse.from_model(label_model) -@admin_router.patch("/category/{category_id}") -def patch_persona_category( - category_id: int, - persona_category_patch_request: PersonaCategoryPatchRequest, +@admin_router.patch("/label/{label_id}") +def patch_persona_label( + label_id: int, + persona_label_patch_request: PersonaLabelPatchRequest, _: User | None = Depends(current_admin_user), db_session: Session = Depends(get_session), ) -> None: - update_persona_category( - category_id=category_id, - category_description=persona_category_patch_request.category_description, - category_name=persona_category_patch_request.category_name, + update_persona_label( + label_id=label_id, + label_name=persona_label_patch_request.label_name, db_session=db_session, ) -@admin_router.delete("/category/{category_id}") -def delete_category( - category_id: int, +@admin_router.delete("/label/{label_id}") +def delete_label( + label_id: int, _: User | None = Depends(current_admin_user), db_session: Session = Depends(get_session), ) -> None: - delete_persona_category(category_id=category_id, db_session=db_session) + delete_persona_label(label_id=label_id, db_session=db_session) class PersonaShareRequest(BaseModel): @@ -393,16 +389,19 @@ def build_assistant_prompts( ) -> list[StarterMessage]: try: logger.info( - "Generating starter messages for user: %s", user.id if user else "Anonymous" + f"Generating {generate_persona_prompt_request.generation_count} starter messages" + f" for user: {user.id if user else 'Anonymous'}", ) - return generate_starter_messages( + starter_messages = generate_starter_messages( name=generate_persona_prompt_request.name, description=generate_persona_prompt_request.description, instructions=generate_persona_prompt_request.instructions, document_set_ids=generate_persona_prompt_request.document_set_ids, + generation_count=generate_persona_prompt_request.generation_count, db_session=db_session, user=user, ) + return starter_messages except Exception as e: logger.exception("Failed to generate starter messages") raise HTTPException(status_code=500, detail=str(e)) diff --git a/backend/onyx/server/features/persona/models.py b/backend/onyx/server/features/persona/models.py index f656e87f3cb..fd41f43bdd7 100644 --- a/backend/onyx/server/features/persona/models.py +++ b/backend/onyx/server/features/persona/models.py @@ -6,7 +6,7 @@ from pydantic import Field from onyx.context.search.enums import RecencyBiasSetting from onyx.db.models import Persona -from onyx.db.models import PersonaCategory +from onyx.db.models import PersonaLabel from onyx.db.models import StarterMessage from onyx.server.features.document_set.models import DocumentSet from onyx.server.features.prompt.models import PromptSnapshot @@ -14,6 +14,7 @@ from onyx.server.features.tool.models import ToolSnapshot from onyx.server.models import MinimalUserSnapshot from onyx.utils.logger import setup_logger + logger = setup_logger() @@ -23,6 +24,7 @@ class GenerateStarterMessageRequest(BaseModel): description: str instructions: str document_set_ids: list[int] + generation_count: int class CreatePersonaRequest(BaseModel): @@ -50,7 +52,7 @@ class CreatePersonaRequest(BaseModel): is_default_persona: bool = False display_priority: int | None = None search_start_date: datetime | None = None - category_id: int | None = None + label_ids: list[int] | None = None class PersonaSnapshot(BaseModel): @@ -78,7 +80,7 @@ class PersonaSnapshot(BaseModel): uploaded_image_id: str | None = None is_default_persona: bool search_start_date: datetime | None = None - category_id: int | None = None + labels: list["PersonaLabelSnapshot"] @classmethod def from_model( @@ -126,7 +128,7 @@ class PersonaSnapshot(BaseModel): icon_shape=persona.icon_shape, uploaded_image_id=persona.uploaded_image_id, search_start_date=persona.search_start_date, - category_id=persona.category_id, + labels=[PersonaLabelSnapshot.from_model(label) for label in persona.labels], ) @@ -142,20 +144,29 @@ class ImageGenerationToolStatus(BaseModel): is_available: bool -class PersonaCategoryCreate(BaseModel): +class PersonaLabelCreate(BaseModel): name: str - description: str -class PersonaCategoryResponse(BaseModel): +class PersonaLabelResponse(BaseModel): id: int name: str - description: str | None @classmethod - def from_model(cls, category: PersonaCategory) -> "PersonaCategoryResponse": - return PersonaCategoryResponse( + def from_model(cls, category: PersonaLabel) -> "PersonaLabelResponse": + return PersonaLabelResponse( id=category.id, name=category.name, - description=category.description, + ) + + +class PersonaLabelSnapshot(BaseModel): + id: int + name: str + + @classmethod + def from_model(cls, label: PersonaLabel) -> "PersonaLabelSnapshot": + return PersonaLabelSnapshot( + id=label.id, + name=label.name, ) diff --git a/backend/onyx/server/manage/models.py b/backend/onyx/server/manage/models.py index 6dfb61a7fb2..2ab8c66083a 100644 --- a/backend/onyx/server/manage/models.py +++ b/backend/onyx/server/manage/models.py @@ -47,6 +47,8 @@ class UserPreferences(BaseModel): recent_assistants: list[int] | None = None default_model: str | None = None auto_scroll: bool | None = None + pinned_assistants: list[int] | None = None + shortcut_enabled: bool | None = None class UserInfo(BaseModel): @@ -83,10 +85,12 @@ class UserInfo(BaseModel): role=user.role, preferences=( UserPreferences( + shortcut_enabled=user.shortcut_enabled, auto_scroll=user.auto_scroll, chosen_assistants=user.chosen_assistants, default_model=user.default_model, hidden_assistants=user.hidden_assistants, + pinned_assistants=user.pinned_assistants, visible_assistants=user.visible_assistants, ) ), diff --git a/backend/onyx/server/manage/users.py b/backend/onyx/server/manage/users.py index 2491082daf8..773cee01116 100644 --- a/backend/onyx/server/manage/users.py +++ b/backend/onyx/server/manage/users.py @@ -625,6 +625,30 @@ def update_user_recent_assistants( db_session.commit() +@router.patch("/shortcut-enabled") +def update_user_shortcut_enabled( + shortcut_enabled: bool, + 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.shortcut_enabled = shortcut_enabled + 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(shortcut_enabled=shortcut_enabled) + ) + db_session.commit() + + @router.patch("/auto-scroll") def update_user_auto_scroll( request: AutoScrollRequest, @@ -673,6 +697,37 @@ def update_user_default_model( db_session.commit() +class ReorderPinnedAssistantsRequest(BaseModel): + ordered_assistant_ids: list[int] + + +@router.patch("/user/pinned-assistants") +def update_user_pinned_assistants( + request: ReorderPinnedAssistantsRequest, + user: User | None = Depends(current_user), + db_session: Session = Depends(get_session), +) -> None: + ordered_assistant_ids = request.ordered_assistant_ids + + 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.pinned_assistants = ordered_assistant_ids + print("ordered_assistant_ids", ordered_assistant_ids) + 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(pinned_assistants=ordered_assistant_ids) + ) + db_session.commit() + + class ChosenAssistantsRequest(BaseModel): chosen_assistants: list[int] diff --git a/backend/onyx/tools/tool_implementations/images/image_generation_tool.py b/backend/onyx/tools/tool_implementations/images/image_generation_tool.py index 0deee37b1ef..c1e83e598dc 100644 --- a/backend/onyx/tools/tool_implementations/images/image_generation_tool.py +++ b/backend/onyx/tools/tool_implementations/images/image_generation_tool.py @@ -81,7 +81,7 @@ class ImageShape(str, Enum): class ImageGenerationTool(Tool): _NAME = "run_image_generation" _DESCRIPTION = "Generate an image from a prompt." - _DISPLAY_NAME = "Image Generation Tool" + _DISPLAY_NAME = "Image Generation" def __init__( self, diff --git a/backend/onyx/tools/tool_implementations/internet_search/internet_search_tool.py b/backend/onyx/tools/tool_implementations/internet_search/internet_search_tool.py index 084c03853d8..feb49318a6a 100644 --- a/backend/onyx/tools/tool_implementations/internet_search/internet_search_tool.py +++ b/backend/onyx/tools/tool_implementations/internet_search/internet_search_tool.py @@ -108,7 +108,7 @@ def internet_search_response_to_search_docs( class InternetSearchTool(Tool): _NAME = "run_internet_search" - _DISPLAY_NAME = "[Beta] Internet Search Tool" + _DISPLAY_NAME = "Internet Search" _DESCRIPTION = "Perform an internet search for up-to-date information." def __init__( diff --git a/backend/tests/integration/common_utils/managers/persona.py b/backend/tests/integration/common_utils/managers/persona.py index 325208fe2c9..e2e22981afa 100644 --- a/backend/tests/integration/common_utils/managers/persona.py +++ b/backend/tests/integration/common_utils/managers/persona.py @@ -7,7 +7,7 @@ from onyx.server.features.persona.models import PersonaSnapshot from tests.integration.common_utils.constants import API_SERVER_URL from tests.integration.common_utils.constants import GENERAL_HEADERS from tests.integration.common_utils.test_models import DATestPersona -from tests.integration.common_utils.test_models import DATestPersonaCategory +from tests.integration.common_utils.test_models import DATestPersonaLabel from tests.integration.common_utils.test_models import DATestUser @@ -216,17 +216,16 @@ class PersonaManager: return response.ok -class PersonaCategoryManager: +class PersonaLabelManager: @staticmethod def create( - category: DATestPersonaCategory, + label: DATestPersonaLabel, user_performing_action: DATestUser | None = None, - ) -> DATestPersonaCategory: + ) -> DATestPersonaLabel: response = requests.post( - f"{API_SERVER_URL}/admin/persona/categories", + f"{API_SERVER_URL}/persona/labels", json={ - "name": category.name, - "description": category.description, + "name": label.name, }, headers=user_performing_action.headers if user_performing_action @@ -234,47 +233,46 @@ class PersonaCategoryManager: ) response.raise_for_status() response_data = response.json() - category.id = response_data["id"] - return category + label.id = response_data["id"] + return label @staticmethod def get_all( user_performing_action: DATestUser | None = None, - ) -> list[DATestPersonaCategory]: + ) -> list[DATestPersonaLabel]: response = requests.get( - f"{API_SERVER_URL}/persona/categories", + f"{API_SERVER_URL}/persona/labels", headers=user_performing_action.headers if user_performing_action else GENERAL_HEADERS, ) response.raise_for_status() - return [DATestPersonaCategory(**category) for category in response.json()] + return [DATestPersonaLabel(**label) for label in response.json()] @staticmethod def update( - category: DATestPersonaCategory, + label: DATestPersonaLabel, user_performing_action: DATestUser | None = None, - ) -> DATestPersonaCategory: + ) -> DATestPersonaLabel: response = requests.patch( - f"{API_SERVER_URL}/admin/persona/category/{category.id}", + f"{API_SERVER_URL}/admin/persona/label/{label.id}", json={ - "category_name": category.name, - "category_description": category.description, + "label_name": label.name, }, headers=user_performing_action.headers if user_performing_action else GENERAL_HEADERS, ) response.raise_for_status() - return category + return label @staticmethod def delete( - category: DATestPersonaCategory, + label: DATestPersonaLabel, user_performing_action: DATestUser | None = None, ) -> bool: response = requests.delete( - f"{API_SERVER_URL}/admin/persona/category/{category.id}", + f"{API_SERVER_URL}/admin/persona/label/{label.id}", headers=user_performing_action.headers if user_performing_action else GENERAL_HEADERS, @@ -283,14 +281,11 @@ class PersonaCategoryManager: @staticmethod def verify( - category: DATestPersonaCategory, + label: DATestPersonaLabel, user_performing_action: DATestUser | None = None, ) -> bool: - all_categories = PersonaCategoryManager.get_all(user_performing_action) - for fetched_category in all_categories: - if fetched_category.id == category.id: - return ( - fetched_category.name == category.name - and fetched_category.description == category.description - ) + all_labels = PersonaLabelManager.get_all(user_performing_action) + for fetched_label in all_labels: + if fetched_label.id == label.id: + return fetched_label.name == label.name return False diff --git a/backend/tests/integration/common_utils/test_models.py b/backend/tests/integration/common_utils/test_models.py index f8f1286dec5..659dfffdda1 100644 --- a/backend/tests/integration/common_utils/test_models.py +++ b/backend/tests/integration/common_utils/test_models.py @@ -41,10 +41,9 @@ class DATestUser(BaseModel): is_active: bool -class DATestPersonaCategory(BaseModel): +class DATestPersonaLabel(BaseModel): id: int | None = None name: str - description: str | None class DATestCredential(BaseModel): diff --git a/backend/tests/integration/tests/personas/test_persona_categories.py b/backend/tests/integration/tests/personas/test_persona_categories.py index 1ac2d3b3000..42bff1de8d6 100644 --- a/backend/tests/integration/tests/personas/test_persona_categories.py +++ b/backend/tests/integration/tests/personas/test_persona_categories.py @@ -4,90 +4,83 @@ import pytest from requests.exceptions import HTTPError from tests.integration.common_utils.managers.persona import ( - PersonaCategoryManager, + PersonaLabelManager, ) from tests.integration.common_utils.managers.user import UserManager -from tests.integration.common_utils.test_models import DATestPersonaCategory +from tests.integration.common_utils.test_models import DATestPersonaLabel from tests.integration.common_utils.test_models import DATestUser -def test_persona_category_management(reset: None) -> None: +def test_persona_label_management(reset: None) -> None: admin_user: DATestUser = UserManager.create(name="admin_user") - persona_category = DATestPersonaCategory( + persona_label = DATestPersonaLabel( id=None, - name=f"Test Category {uuid4()}", - description="A description for test category", + name=f"Test label {uuid4()}", ) - persona_category = PersonaCategoryManager.create( - category=persona_category, + persona_label = PersonaLabelManager.create( + label=persona_label, user_performing_action=admin_user, ) - print( - f"Created persona category {persona_category.name} with id {persona_category.id}" - ) + print(f"Created persona label {persona_label.name} with id {persona_label.id}") - assert PersonaCategoryManager.verify( - category=persona_category, + assert PersonaLabelManager.verify( + label=persona_label, user_performing_action=admin_user, - ), "Persona category was not found after creation" + ), "Persona label was not found after creation" regular_user: DATestUser = UserManager.create(name="regular_user") - updated_persona_category = DATestPersonaCategory( - id=persona_category.id, - name=f"Updated {persona_category.name}", - description="An updated description", + updated_persona_label = DATestPersonaLabel( + id=persona_label.id, + name=f"Updated {persona_label.name}", ) with pytest.raises(HTTPError) as exc_info: - PersonaCategoryManager.update( - category=updated_persona_category, + PersonaLabelManager.update( + label=updated_persona_label, user_performing_action=regular_user, ) assert exc_info.value.response is not None assert exc_info.value.response.status_code == 403 - assert PersonaCategoryManager.verify( - category=persona_category, + assert PersonaLabelManager.verify( + label=persona_label, user_performing_action=admin_user, - ), "Persona category should not have been updated by non-admin user" + ), "Persona label should not have been updated by non-admin user" - result = PersonaCategoryManager.delete( - category=persona_category, + result = PersonaLabelManager.delete( + label=persona_label, user_performing_action=regular_user, ) assert ( result is False - ), "Regular user should not be able to delete the persona category" + ), "Regular user should not be able to delete the persona label" - assert PersonaCategoryManager.verify( - category=persona_category, + assert PersonaLabelManager.verify( + label=persona_label, user_performing_action=admin_user, - ), "Persona category should not have been deleted by non-admin user" + ), "Persona label should not have been deleted by non-admin user" - updated_persona_category.name = f"Updated {persona_category.name}" - updated_persona_category.description = "An updated description" - updated_persona_category = PersonaCategoryManager.update( - category=updated_persona_category, + updated_persona_label.name = f"Updated {persona_label.name}" + updated_persona_label = PersonaLabelManager.update( + label=updated_persona_label, user_performing_action=admin_user, ) - print(f"Updated persona category to {updated_persona_category.name}") + print(f"Updated persona label to {updated_persona_label.name}") - assert PersonaCategoryManager.verify( - category=updated_persona_category, + assert PersonaLabelManager.verify( + label=updated_persona_label, user_performing_action=admin_user, - ), "Persona category was not updated by admin" + ), "Persona label was not updated by admin" - success = PersonaCategoryManager.delete( - category=persona_category, + success = PersonaLabelManager.delete( + label=persona_label, user_performing_action=admin_user, ) - assert success, "Admin user should be able to delete the persona category" - print( - f"Deleted persona category {persona_category.name} with id {persona_category.id}" - ) + assert success, "Admin user should be able to delete the persona label" + print(f"Deleted persona label {persona_label.name} with id {persona_label.id}") - assert not PersonaCategoryManager.verify( - category=persona_category, + assert not PersonaLabelManager.verify( + label=persona_label, user_performing_action=admin_user, - ), "Persona category should not exist after deletion by admin" + ), "Persona label should not exist after deletion by admin" diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json deleted file mode 100644 index b3aaf2c4dec..00000000000 --- a/node_modules/.package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "danswer", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000000..026e01cb8df --- /dev/null +++ b/package-lock.json @@ -0,0 +1,183 @@ +{ + "name": "onyx", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "react-datepicker": "^7.6.0" + }, + "devDependencies": { + "@types/react-datepicker": "^6.2.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", + "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.13", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.13.tgz", + "integrity": "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.3.tgz", + "integrity": "sha512-CLHnes3ixIFFKVQDdICjel8muhFLOBdQH7fgtHNPY8UbCNqbeKZ262G7K66lGQOUQWWnYocf7ZbUsLJgGfsLHg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.9", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.0.4", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.4.tgz", + "integrity": "sha512-3O4QisJDYr1uTUMZHA2YswiQZRq+Pd8D+GdVFYikTutYsTz+QZgWkAPnP7rx9txoI6EXKcPiluMqWPFV3tT9Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-datepicker": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@types/react-datepicker/-/react-datepicker-6.2.0.tgz", + "integrity": "sha512-+JtO4Fm97WLkJTH8j8/v3Ldh7JCNRwjMYjRaKh4KHH0M3jJoXtwiD3JBCsdlg3tsFIw9eQSqyAPeVDN2H2oM9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.2", + "@types/react": "*", + "date-fns": "^3.3.1" + } + }, + "node_modules/@types/react-datepicker/node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-datepicker": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-7.6.0.tgz", + "integrity": "sha512-9cQH6Z/qa4LrGhzdc3XoHbhrxNcMi9MKjZmYgF/1MNNaJwvdSjv3Xd+jjvrEEbKEf71ZgCA3n7fQbdwd70qCRw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.27.0", + "clsx": "^2.1.1", + "date-fns": "^3.6.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, + "node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "license": "MIT", + "peer": true + }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000000..fd0d363ff77 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "react-datepicker": "^7.6.0" + }, + "devDependencies": { + "@types/react-datepicker": "^6.2.0" + } +} diff --git a/web/package-lock.json b/web/package-lock.json index 0d1eb688f30..38bf8c3e85f 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -17,6 +17,7 @@ "@phosphor-icons/react": "^2.0.8", "@radix-ui/react-checkbox": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.4", "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-radio-group": "^1.2.2", @@ -52,6 +53,7 @@ "posthog-js": "^1.176.0", "prismjs": "^1.29.0", "react": "^18.3.1", + "react-datepicker": "^7.6.0", "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", "react-dropzone": "^14.2.3", @@ -1197,6 +1199,21 @@ "@floating-ui/utils": "^0.2.0" } }, + "node_modules/@floating-ui/react": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.3.tgz", + "integrity": "sha512-CLHnes3ixIFFKVQDdICjel8muhFLOBdQH7fgtHNPY8UbCNqbeKZ262G7K66lGQOUQWWnYocf7ZbUsLJgGfsLHg==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.9", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, "node_modules/@floating-ui/react-dom": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", @@ -1210,9 +1227,10 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", - "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" }, "node_modules/@headlessui/react": { "version": "2.2.0", @@ -2859,6 +2877,112 @@ } } }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.4.tgz", + "integrity": "sha512-iXU1Ab5ecM+yEepGAWK8ZhMyKX4ubFdCNtol4sT9D0OVErG9PNElfx3TQhjw7n7BC5nFVz68/5//clWy+8TXzA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-menu": "2.1.4", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/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-dropdown-menu/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", @@ -2994,6 +3118,360 @@ } } }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.4.tgz", + "integrity": "sha512-BnOgVoL6YYdHAG6DtXONaR29Eq4nvbi8rutrV/xlr3RQCMMb3yqP85Qiw/3NReozrSW+4dfLkK+rc1hb4wPU/A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.3", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.1", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.1", + "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-roving-focus": "1.1.1", + "@radix-ui/react-slot": "1.1.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "^2.6.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz", + "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-arrow": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz", + "integrity": "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-collection": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz", + "integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz", + "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/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-menu/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.3.tgz", + "integrity": "sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "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-menu/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz", + "integrity": "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "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-menu/node_modules/@radix-ui/react-popper": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz", + "integrity": "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-use-rect": "1.1.0", + "@radix-ui/react-use-size": "1.1.0", + "@radix-ui/rect": "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-menu/node_modules/@radix-ui/react-portal": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz", + "integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-presence": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz", + "integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-primitive": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz", + "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz", + "integrity": "sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-collection": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz", + "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/react-remove-scroll": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz", + "integrity": "sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popover": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.2.tgz", @@ -8962,15 +9440,6 @@ "node": ">=12" } }, - "node_modules/invariant": { - "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" - } - }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -14315,6 +14784,21 @@ "node": ">=0.10.0" } }, + "node_modules/react-datepicker": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-7.6.0.tgz", + "integrity": "sha512-9cQH6Z/qa4LrGhzdc3XoHbhrxNcMi9MKjZmYgF/1MNNaJwvdSjv3Xd+jjvrEEbKEf71ZgCA3n7fQbdwd70qCRw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.27.0", + "clsx": "^2.1.1", + "date-fns": "^3.6.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/react-day-picker": { "version": "8.10.1", "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz", @@ -14504,20 +14988,20 @@ } }, "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==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", "license": "MIT", "dependencies": { - "react-style-singleton": "^2.2.1", + "react-style-singleton": "^2.2.2", "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" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -14605,21 +15089,20 @@ } }, "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==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", "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" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -16359,9 +16842,9 @@ "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==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -16370,8 +16853,8 @@ "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { diff --git a/web/package.json b/web/package.json index 3fdf30f8535..56834d99e09 100644 --- a/web/package.json +++ b/web/package.json @@ -19,6 +19,7 @@ "@phosphor-icons/react": "^2.0.8", "@radix-ui/react-checkbox": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.4", "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-radio-group": "^1.2.2", @@ -54,6 +55,7 @@ "posthog-js": "^1.176.0", "prismjs": "^1.29.0", "react": "^18.3.1", + "react-datepicker": "^7.6.0", "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", "react-dropzone": "^14.2.3", diff --git a/web/public/logo.svg b/web/public/logo.svg new file mode 100644 index 00000000000..a6ddc7e42bb --- /dev/null +++ b/web/public/logo.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/web/public/web.svg b/web/public/web.svg new file mode 100644 index 00000000000..79b061e2eff --- /dev/null +++ b/web/public/web.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/web/src/app/admin/add-connector/page.tsx b/web/src/app/admin/add-connector/page.tsx index 587f1a085e2..8d63f5d14ef 100644 --- a/web/src/app/admin/add-connector/page.tsx +++ b/web/src/app/admin/add-connector/page.tsx @@ -113,7 +113,7 @@ export default function Page() { value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} onKeyDown={handleKeyPress} - className="ml-1 w-96 h-9 flex-none rounded-md border border-border bg-background-50 px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" + className="ml-1 w-96 h-9 flex-none rounded-md border border-border bg-background-50 px-3 py-1 text-sm shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" /> {Object.entries(categorizedSources) diff --git a/web/src/app/admin/assistants/AssistantEditor.tsx b/web/src/app/admin/assistants/AssistantEditor.tsx index eb79bd04581..0e3943bd646 100644 --- a/web/src/app/admin/assistants/AssistantEditor.tsx +++ b/web/src/app/admin/assistants/AssistantEditor.tsx @@ -1,20 +1,14 @@ "use client"; +import React, { useCallback } from "react"; import { Option } from "@/components/Dropdown"; -import { generateRandomIconShape, createSVG } from "@/lib/assistantIconUtils"; -import { CCPairBasicInfo, DocumentSet, User } from "@/lib/types"; +import { generateRandomIconShape } from "@/lib/assistantIconUtils"; +import { CCPairBasicInfo, DocumentSet, User, UserGroup } from "@/lib/types"; import { Separator } from "@/components/ui/separator"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { IsPublicGroupSelector } from "@/components/IsPublicGroupSelector"; -import { - ArrayHelpers, - FieldArray, - Form, - Formik, - FormikProps, - useFormikContext, -} from "formik"; +import { ArrayHelpers, FieldArray, Form, Formik, FormikProps } from "formik"; import { BooleanFormField, @@ -24,10 +18,14 @@ import { } from "@/components/admin/connectors/Field"; import { usePopup } from "@/components/admin/connectors/Popup"; -import { getDisplayNameForModel, useCategories } from "@/lib/hooks"; +import { getDisplayNameForModel, useLabels } from "@/lib/hooks"; import { DocumentSetSelectable } from "@/components/documentSet/DocumentSetSelectable"; import { addAssistantToList } from "@/lib/assistants/updateAssistantPreferences"; -import { checkLLMSupportsImageInput, destructureValue } from "@/lib/llm/utils"; +import { + checkLLMSupportsImageInput, + destructureValue, + structureValue, +} from "@/lib/llm/utils"; import { ToolSnapshot } from "@/lib/tools/interfaces"; import { checkUserIsNoAuthUser } from "@/lib/user"; @@ -40,34 +38,63 @@ import { import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; -import { FiInfo, FiRefreshCcw } from "react-icons/fi"; +import { FiInfo, FiRefreshCcw, FiUsers } from "react-icons/fi"; import * as Yup from "yup"; import CollapsibleSection from "./CollapsibleSection"; import { SuccessfulPersonaUpdateRedirectType } from "./enums"; -import { Persona, PersonaCategory, StarterMessage } from "./interfaces"; import { - createPersonaCategory, + Persona, + PersonaLabel, + StarterMessage, + StarterMessageBase, +} from "./interfaces"; +import { + createPersonaLabel, createPersona, - deletePersonaCategory, - updatePersonaCategory, + deletePersonaLabel, + updatePersonaLabel, updatePersona, } from "./lib"; -import { Popover } from "@/components/popover/Popover"; import { CameraIcon, + GroupsIconSkeleton, NewChatIcon, SwapIcon, TrashIcon, } from "@/components/icons/icons"; -import { AdvancedOptionsToggle } from "@/components/AdvancedOptionsToggle"; import { buildImgUrl } from "@/app/chat/files/images/utils"; -import { LlmList } from "@/components/llm/LLMList"; import { useAssistants } from "@/components/context/AssistantsContext"; import { debounce } from "lodash"; import { FullLLMProvider } from "../configuration/llm/interfaces"; import StarterMessagesList from "./StarterMessageList"; +import { LabelCard } from "./LabelCard"; +import { Switch } from "@/components/ui/switch"; +import { generateIdenticon } from "@/components/assistants/AssistantIcon"; +import { BackButton } from "@/components/BackButton"; +import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; -import { CategoryCard } from "./CategoryCard"; +import { AdvancedOptionsToggle } from "@/components/AdvancedOptionsToggle"; +import { AssistantVisibilityPopover } from "@/app/assistants/mine/AssistantVisibilityPopover"; +import { MinimalUserSnapshot } from "@/lib/types"; +import { useUserGroups } from "@/lib/hooks"; +import { useUsers } from "@/lib/hooks"; +import { AllUsersResponse } from "@/lib/types"; +// import { Badge } from "@/components/ui/Badge"; +// import { +// addUsersToAssistantSharedList, +// shareAssistantWithGroups, +// } from "@/lib/assistants/shareAssistant"; +import { + SearchMultiSelectDropdown, + Option as DropdownOption, +} from "@/components/Dropdown"; +import { Badge } from "@/components/ui/badge"; +import { SourceChip } from "@/app/chat/input/ChatInputBar"; +import { GroupIcon, TagIcon, UserIcon } from "lucide-react"; +import { LLMSelector } from "@/components/llm/LLMSelector"; +import useSWR from "swr"; +import { errorHandlingFetcher } from "@/lib/fetcher"; +import { DeleteEntityModal } from "@/components/modals/DeleteEntityModal"; function findSearchTool(tools: ToolSnapshot[]) { return tools.find((tool) => tool.in_code_tool_id === "SearchTool"); @@ -83,7 +110,10 @@ function findInternetSearchTool(tools: ToolSnapshot[]) { function SubLabel({ children }: { children: string | JSX.Element }) { return ( -
+
{children}
); @@ -116,7 +146,8 @@ export function AssistantEditor({ const router = useRouter(); const { popup, setPopup } = usePopup(); - const { data: categories, refreshCategories } = useCategories(); + const { data, refreshLabels } = useLabels(); + const labels = data || []; const colorOptions = [ "#FF6FBF", @@ -128,9 +159,11 @@ export function AssistantEditor({ "#6FFFFF", ]; + const [showSearchTool, setShowSearchTool] = useState(false); + const [showAdvancedOptions, setShowAdvancedOptions] = useState(false); const [hasEditedStarterMessage, setHasEditedStarterMessage] = useState(false); - const [showPersonaCategory, setShowPersonaCategory] = useState(!admin); + const [showPersonaLabel, setShowPersonaLabel] = useState(!admin); // state to persist across formik reformatting const [defautIconColor, _setDeafultIconColor] = useState( @@ -225,19 +258,6 @@ export function AssistantEditor({ existingPersona?.llm_model_version_override ?? null, starter_messages: existingPersona?.starter_messages ?? [ { - name: "", - message: "", - }, - { - name: "", - message: "", - }, - { - name: "", - message: "", - }, - { - name: "", message: "", }, ], @@ -245,10 +265,16 @@ export function AssistantEditor({ icon_color: existingPersona?.icon_color ?? defautIconColor, icon_shape: existingPersona?.icon_shape ?? defaultIconShape, uploaded_image: null, - category_id: existingPersona?.category_id ?? null, + labels: existingPersona?.labels ?? null, // EE Only groups: existingPersona?.groups ?? [], + label_ids: existingPersona?.labels?.map((label) => label.id) ?? [], + selectedUsers: + existingPersona?.users?.filter( + (u) => u.id !== existingPersona.owner?.id + ) ?? [], + selectedGroups: existingPersona?.groups ?? [], }; interface AssistantPrompt { @@ -257,7 +283,7 @@ export function AssistantEditor({ } const debouncedRefreshPrompts = debounce( - async (values: any, setFieldValue: any) => { + async (formValues: any, setFieldValue: any) => { if (!autoStarterMessageEnabled) { return; } @@ -269,16 +295,28 @@ export function AssistantEditor({ "Content-Type": "application/json", }, body: JSON.stringify({ - name: values.name, - description: values.description, - document_set_ids: values.document_set_ids, - instructions: values.system_prompt || values.task_prompt, + name: formValues.name || "", + description: formValues.description || "", + document_set_ids: formValues.document_set_ids || [], + instructions: + formValues.system_prompt || formValues.task_prompt || "", + generation_count: + 4 - + formValues.starter_messages.filter( + (message: StarterMessage) => message.message.trim() !== "" + ).length, }), }); - const data: AssistantPrompt = await response.json(); + const data: AssistantPrompt[] = await response.json(); if (response.ok) { - setFieldValue("starter_messages", data); + const filteredStarterMessages = formValues.starter_messages.filter( + (message: StarterMessage) => message.message.trim() !== "" + ); + setFieldValue("starter_messages", [ + ...filteredStarterMessages, + ...data, + ]); } } catch (error) { console.error("Failed to refresh prompts:", error); @@ -289,10 +327,66 @@ export function AssistantEditor({ 1000 ); + const [labelToDelete, setLabelToDelete] = useState(null); const [isRequestSuccessful, setIsRequestSuccessful] = useState(false); + const { data: userGroups } = useUserGroups(); + // const { data: allUsers } = useUsers() as { + // data: MinimalUserSnapshot[] | undefined; + // }; + + const { data: users } = useSWR( + "/api/users", + errorHandlingFetcher + ); + + const mapUsersToMinimalSnapshot = (users: any): MinimalUserSnapshot[] => { + if (!users || !Array.isArray(users.users)) return []; + return users.users.map((user: any) => ({ + id: user.id, + name: user.name, + email: user.email, + })); + }; + return ( -
+
+ + {!admin && ( +
+ +
+ )} + {labelToDelete && ( + setLabelToDelete(null)} + onSubmit={async () => { + const response = await deletePersonaLabel(labelToDelete.id); + if (response?.ok) { + setPopup({ + message: `Label deleted successfully`, + type: "success", + }); + await refreshLabels(); + } else { + setPopup({ + message: `Failed to delete label - ${await response.text()}`, + type: "error", + }); + } + setLabelToDelete(null); + }} + /> + )} {popup} message.message.trim() !== "" + ) + .map((message: { message: string; name?: string }) => ({ + message: message.message, + name: message.name || message.message, + })); // don't set groups if marked as public const groups = values.is_public ? [] : values.groups; + const submissionData = { + ...values, + starter_messages: starterMessages, + groups: values.is_public ? [] : values.selectedGroups, + users: values.is_public + ? undefined + : [ + ...(user && !checkUserIsNoAuthUser(user.id) ? [user.id] : []), + ...values.selectedUsers.map((u: MinimalUserSnapshot) => u.id), + ], + tool_ids: enabledTools, + remove_image: removePersonaImage, + search_start_date: values.search_start_date + ? new Date(values.search_start_date) + : null, + num_chunks: numChunks, + }; + let promptResponse; let personaResponse; if (isUpdate) { [promptResponse, personaResponse] = await updatePersona({ id: existingPersona.id, existingPromptId: existingPrompt?.id, - ...values, - search_start_date: values.search_start_date - ? new Date(values.search_start_date) - : null, - num_chunks: numChunks, - users: - user && !checkUserIsNoAuthUser(user.id) ? [user.id] : undefined, - groups, - tool_ids: enabledTools, - remove_image: removePersonaImage, + ...submissionData, }); } else { [promptResponse, personaResponse] = await createPersona({ - ...values, + ...submissionData, is_default_persona: admin!, - num_chunks: numChunks, - search_start_date: values.search_start_date - ? new Date(values.search_start_date) - : null, - users: - user && !checkUserIsNoAuthUser(user.id) ? [user.id] : undefined, - groups, - tool_ids: enabledTools, }); } @@ -483,7 +583,6 @@ export function AssistantEditor({ values, setFieldValue, errors, - ...formikProps }: FormikProps) => { function toggleToolInValues(toolId: number) { @@ -494,12 +593,6 @@ export function AssistantEditor({ setFieldValue("enabled_tools_map", updatedEnabledToolsMap); } - function searchToolEnabled() { - return searchTool && values.enabled_tools_map[searchTool.id] - ? true - : false; - } - // model must support image input for image generation // to work const currentLLMSupportsImageOutput = checkLLMSupportsImageInput( @@ -507,812 +600,667 @@ export function AssistantEditor({ ); return ( -
+ {/* Refresh starter messages when name or description changes */} -
- + {existingPersona ? ( + <> + Edit assistant {existingPersona.name} + + ) : ( + "Create an Assistant" + )} +

+
+ +
+
+ Assistant Icon +
+
+ + The icon that will visually represent your Assistant + +
+
+ {values.uploaded_image ? ( + Uploaded assistant icon + ) : existingPersona?.uploaded_image_id && + !removePersonaImage ? ( + Uploaded assistant icon + ) : ( + generateIdenticon((values.icon_shape || 0).toString(), 36) + )} +
+ +
+
- } - popover={ -
- + + Upload {values.uploaded_image && "New "}Image + - {values.uploaded_image && ( - + )} + + {!values.uploaded_image && + (!existingPersona?.uploaded_image_id || + removePersonaImage) && ( + + + Generate Icon + )} - {!values.uploaded_image && - (!existingPersona?.uploaded_image_id || - removePersonaImage) && ( - - )} + {existingPersona?.uploaded_image_id && + removePersonaImage && + !values.uploaded_image && ( + + )} - {existingPersona?.uploaded_image_id && - removePersonaImage && - !values.uploaded_image && ( - - )} - - {existingPersona?.uploaded_image_id && - !removePersonaImage && - !values.uploaded_image && ( - - )} -
- } - align="start" - side="bottom" - /> - - - - - - - This icon will visually represent your Assistant - - - + {existingPersona?.uploaded_image_id && + !removePersonaImage && + !values.uploaded_image && ( + + )} +
+
+
+ +
+
Labels
+
+

+ Select labels to categorize this assistant +

+
+ { + await createPersonaLabel(name); + const currentLabels = await refreshLabels(); + + setTimeout(() => { + const newLabelId = currentLabels.find( + (l: { name: string }) => l.name === name + )?.id; + const updatedLabelIds = [ + ...values.label_ids, + newLabelId as number, + ]; + setFieldValue("label_ids", updatedLabelIds); + }, 300); + }} + options={Array.from( + new Set(labels.map((label) => label.name)) + ).map((name) => ({ + name, + value: name, + }))} + onSelect={(selected) => { + const newLabelIds = [ + ...values.label_ids, + labels.find((l) => l.name === selected.value) + ?.id as number, + ]; + setFieldValue("label_ids", newLabelIds); + }} + itemComponent={({ option }) => ( +
{ + const label = labels.find( + (l) => l.name === option.value + ); + if (label) { + const isSelected = values.label_ids.includes( + label.id + ); + const newLabelIds = isSelected + ? values.label_ids.filter( + (id: number) => id !== label.id + ) + : [...values.label_ids, label.id]; + setFieldValue("label_ids", newLabelIds); + } + }} + > + + {option.name} + +
+ )} + /> +
+ {values.label_ids.map((labelId: number) => { + const label = labels.find((l) => l.id === labelId); + return label ? ( + { + setFieldValue( + "label_ids", + values.label_ids.filter( + (id: number) => id !== label.id + ) + ); + }} + title={label.name} + icon={} + /> + ) : null; + })} +
+
+
+ + + -
-
-
- Default AI Model{" "} -
- - - - - - - Select a Large Language Model (Generative AI model) to - power this Assistant - - - -
-

- Your assistant will use the user's set default unless - otherwise specified below. - {admin && - user?.preferences.default_model && - ` Your current (user-specific) default model is ${getDisplayNameForModel( - destructureValue(user?.preferences?.default_model!) - .modelName - )}`} -

- {admin ? ( -
-
- ({ - name: llmProvider.name, - value: llmProvider.name, - icon: llmProvider.icon, - }))} - includeDefault={true} - onSelect={(selected) => { - if (selected !== values.llm_model_provider_override) { - setFieldValue("llm_model_version_override", null); - } - setFieldValue( - "llm_model_provider_override", - selected - ); - }} - /> -
- - {values.llm_model_provider_override && ( -
- -
- )} -
- ) : ( -
- { - if (value !== null) { - const { modelName, provider, name } = - destructureValue(value); - setFieldValue( - "llm_model_version_override", - modelName - ); - setFieldValue("llm_model_provider_override", name); - } else { - setFieldValue("llm_model_version_override", null); - setFieldValue("llm_model_provider_override", null); - } - }} - /> -
- )} -
-
-
-
- Capabilities{" "} -
- - - - - - - You can give your assistant advanced capabilities like - image generation - - - -
- Advanced -
-
- -
- {imageGenerationTool && ( - - - -
- { - toggleToolInValues(imageGenerationTool.id); - }} - disabled={ - !currentLLMSupportsImageOutput || - !isImageGenerationAvailable - } - /> -
-
- {!currentLLMSupportsImageOutput ? ( - -

- To use Image Generation, select GPT-4o or another - image compatible model as the default model for - this Assistant. -

-
- ) : ( - !isImageGenerationAvailable && ( - -

- Image Generation requires an OpenAI or Azure - Dalle configuration. -

-
- ) - )} -
-
- )} - +
+
{searchTool && ( - - - -
- { - setFieldValue("num_chunks", null); - toggleToolInValues(searchTool.id); - }} - disabled={ccPairs.length === 0} - /> -
-
- {ccPairs.length === 0 && ( - -

- To use the Search Tool, you need to have at least - one Connector-Credential pair configured. -

-
- )} -
-
- )} - - {ccPairs.length > 0 && searchTool && ( <> - {searchToolEnabled() && ( - -
- {ccPairs.length > 0 && ( - <> - -
- - <> - Select which{" "} - {!user || user.role === "admin" ? ( - - Document Sets - - ) : ( - "Document Sets" - )}{" "} - this Assistant should search through. If - none are specified, the Assistant will - search through all available documents in - order to try and respond to queries. - - -
- - {documentSets.length > 0 ? ( - ( -
-
- {documentSets.map((documentSet) => { - const ind = - values.document_set_ids.indexOf( - documentSet.id - ); - const isSelected = ind !== -1; - return ( - { - if (isSelected) { - arrayHelpers.remove(ind); - } else { - arrayHelpers.push( - documentSet.id - ); - } - }} - /> - ); - })} -
-
- )} - /> - ) : ( -

- No Document Sets available.{" "} - {user?.role !== "admin" && ( - <> - If this functionality would be useful, - reach out to the administrators of Onyx - for assistance. - - )} -

- )} - -
- { - const value = e.target.value; - if ( - value === "" || - /^[0-9]+$/.test(value) - ) { - setFieldValue("num_chunks", value); - } - }} - /> - - - - - - -
- - )} -
-
- )} - - )} - - {internetSearchTool && ( - { - toggleToolInValues(internetSearchTool.id); - }} - /> - )} - - {customTools.length > 0 && ( - <> - {customTools.map((tool) => ( - { - toggleToolInValues(tool.id); - }} - /> - ))} - - )} -
-
- -
-
-
- Starter Messages -
-
- - - Pre-configured messages that help users understand what this - assistant can do and how to interact with it effectively. - -
- - - + +
- -
- - {!autoStarterMessageEnabled && ( - -

- No LLM providers configured. Generation is not - available. -

-
- )} - - -
-
- ( - { - setHasEditedStarterMessage(true); - }} - /> - )} - /> -
-
- - {admin && ( - - )} - - {showPersonaCategory && ( - <> - {categories && categories.length > 0 && ( -
-
-
- Category -
- - - - - - - Group similar assistants together by category - - - -
- ({ - name: category.name, - value: category.id, - }))} - /> -
- )} - - {admin && ( - <> -
-
-
- Create New Category
+

+ Attach additional unique knowledge to this assistant +

+
+
+ + )} + {ccPairs.length > 0 && + searchTool && + showSearchTool && + !(user?.role != "admin" && documentSets.length === 0) && ( + +
+ {ccPairs.length > 0 && ( + <> + +
+ + <> + Select which{" "} + {!user || user.role === "admin" ? ( + + Document Sets + + ) : ( + "Document Sets" + )}{" "} + this Assistant should use to inform its + responses. If none are specified, the + Assistant will reference all available + documents. + + +
+ + {documentSets.length > 0 ? ( + ( +
+
+ {documentSets.map((documentSet) => ( + { + const index = + values.document_set_ids.indexOf( + documentSet.id + ); + if (index !== -1) { + arrayHelpers.remove(index); + } else { + arrayHelpers.push( + documentSet.id + ); + } + }} + /> + ))} +
+
+ )} + /> + ) : ( +

+ + + Create Document Set + +

+ )} + + )} +
+
+ )} + + +
+

Actions

+ + {imageGenerationTool && ( + <> +
- + { + if ( + currentLLMSupportsImageOutput && + isImageGenerationAvailable + ) { + toggleToolInValues( + imageGenerationTool.id + ); + } + }} + className={ + !currentLLMSupportsImageOutput || + !isImageGenerationAvailable + ? "opacity-50 cursor-not-allowed" + : "" + } + /> - - Create a new category to group similar - assistants together - + {(!currentLLMSupportsImageOutput || + !isImageGenerationAvailable) && ( + +

+ {!currentLLMSupportsImageOutput + ? "To use Image Generation, select GPT-4 or another image compatible model as the default model for this Assistant." + : "Image Generation requires an OpenAI or Azure Dalle configuration."} +

+
+ )}
+
+ + {imageGenerationTool.display_name} + + + Generate and manipulate images using AI-powered + tools + +
+ + )} -
- +
+ { + toggleToolInValues(internetSearchTool.id); + }} /> - -
-
+ + )} - try { - const response = await createPersonaCategory( - name, - description + {customTools.length > 0 && + customTools.map((tool) => ( + +
+ { + toggleToolInValues(tool.id); + }} + /> +
+ + {tool.display_name} + +
+
+
+ ))} +
+
+
+ + +
+
+
Default Model
+
+ { + if (selected === null) { + setFieldValue("llm_model_version_override", null); + setFieldValue("llm_model_provider_override", null); + } else { + const { modelName, provider, name } = + destructureValue(selected); + if (modelName && name) { + setFieldValue("llm_model_version_override", modelName); + setFieldValue("llm_model_provider_override", name); + } + } + }} + /> +
+ + {admin && labels && labels.length > 0 && ( +
+ +
+
+ Manage Labels +
+ + + + + + + Manage existing labels or create new ones to group + similar assistants + + + +
+ Edit or delete existing labels +
+ {labels.map((label: PersonaLabel) => ( +
+ { + setFieldValue("editLabelId", label.id); + setFieldValue("editLabelName", e.target.value); + }} + /> +
+ {values.editLabelId === label.id ? ( + <> + -
+ }} + > + Save + + + + ) : ( + <> + + + )}
- - {categories && categories.length > 0 && ( -
-
-
- Manage categories -
- - - - - - - Manage existing categories or create new ones - to group similar assistants - - - -
-
- {categories && - categories.map((category: PersonaCategory) => ( - { - const response = - await updatePersonaCategory( - id, - name, - description - ); - if (response?.ok) { - setPopup({ - message: `Category "${name}" updated successfully`, - type: "success", - }); - } else { - setPopup({ - message: `Failed to update category - ${await response.text()}`, - type: "error", - }); - } - }} - onDelete={async (id) => { - const response = - await deletePersonaCategory(id); - if (response?.ok) { - setPopup({ - message: `Category deleted successfully`, - type: "success", - }); - } else { - setPopup({ - message: `Failed to delete category - ${await response.text()}`, - type: "error", - }); - } - }} - refreshCategories={refreshCategories} - /> - ))} -
-
- )} - - )} - + ))} +
+
)} @@ -1320,46 +1268,255 @@ export function AssistantEditor({ showAdvancedOptions={showAdvancedOptions} setShowAdvancedOptions={setShowAdvancedOptions} /> - {showAdvancedOptions && ( <> - {llmProviders.length > 0 && ( - <> - { - setFieldValue("task_prompt", e.target.value); - }} - explanationText="Learn about prompting in our docs!" - explanationLink="https://docs.onyx.app/guides/assistants" - /> - - )} +
+
+
Access
+
+ + Control who can access and use this assistant + - +
+ { + setFieldValue("is_public", checked); + if (checked) { + setFieldValue("selectedUsers", []); + setFieldValue("selectedGroups", []); + } + }} + /> + + {values.is_public ? "Public" : "Private"} + +
+ + {values.is_public ? ( +

+ Anyone from your organization can view and use this + assistant +

+ ) : ( + <> +
+ + + + !values.selectedUsers.some( + (su: MinimalUserSnapshot) => + su.id === u.id + ) && u.id !== user?.id + ) + .map((u: MinimalUserSnapshot) => ({ + name: u.email, + value: u.id, + type: "user", + })), + ...(userGroups || []) + .filter( + (g: UserGroup) => + !values.selectedGroups.includes(g.id) + ) + .map((g: UserGroup) => ({ + name: g.name, + value: g.id, + type: "group", + })), + ]} + onSelect={( + selected: DropdownOption + ) => { + const option = selected as { + name: string; + value: string | number; + type: "user" | "group"; + }; + if (option.type === "user") { + setFieldValue("selectedUsers", [ + ...values.selectedUsers, + { id: option.value, email: option.name }, + ]); + } else { + setFieldValue("selectedGroups", [ + ...values.selectedGroups, + option.value, + ]); + } + }} + /> +
+
+ {values.selectedUsers.map( + (user: MinimalUserSnapshot) => ( + { + setFieldValue( + "selectedUsers", + values.selectedUsers.filter( + (u: MinimalUserSnapshot) => + u.id !== user.id + ) + ); + }} + title={user.email} + icon={} + /> + ) + )} + {values.selectedGroups.map((groupId: number) => { + const group = (userGroups || []).find( + (g: UserGroup) => g.id === groupId + ); + return group ? ( + { + setFieldValue( + "selectedGroups", + values.selectedGroups.filter( + (id: number) => id !== group.id + ) + ); + }} + icon={} + /> + ) : null; + })} +
+ + )} +
+
+ +
+
+
+ [Optional] Starter Messages +
+
+ + + Sample messages that help users understand what this + assistant can do and how to interact with it effectively. + + +
+ ( + + debouncedRefreshPrompts(values, setFieldValue) + } + autoStarterMessageEnabled={ + autoStarterMessageEnabled + } + errors={errors} + isRefreshing={isRefreshing} + values={values.starter_messages} + arrayHelpers={arrayHelpers} + touchStarterMessages={() => { + setHasEditedStarterMessage(true); + }} + setFieldValue={setFieldValue} + /> + )} + /> +
+
+ + +
+
+

Knowledge Options

+
+ { + const value = e.target.value; + if (value === "" || /^[0-9]+$/.test(value)) { + setFieldValue("num_chunks", value); + } + }} + /> + + + + + + +
+
+
+ + + { + setFieldValue("task_prompt", e.target.value); }} - objectName="assistant" - enforceGroupSelection={false} + explanationText="Learn about prompting in our docs!" + explanationLink="https://docs.onyx.app/guides/assistants" + className="[&_textarea]:placeholder:text-text-muted/50" /> )} -
+
+
diff --git a/web/src/app/admin/assistants/CollapsibleSection.tsx b/web/src/app/admin/assistants/CollapsibleSection.tsx index 1ff3ce09a7c..f4ec60582da 100644 --- a/web/src/app/admin/assistants/CollapsibleSection.tsx +++ b/web/src/app/admin/assistants/CollapsibleSection.tsx @@ -34,7 +34,7 @@ const CollapsibleSection: React.FC = ({
diff --git a/web/src/app/admin/assistants/CategoryCard.tsx b/web/src/app/admin/assistants/LabelCard.tsx similarity index 64% rename from web/src/app/admin/assistants/CategoryCard.tsx rename to web/src/app/admin/assistants/LabelCard.tsx index a4097125053..eac34573bf3 100644 --- a/web/src/app/admin/assistants/CategoryCard.tsx +++ b/web/src/app/admin/assistants/LabelCard.tsx @@ -9,31 +9,30 @@ import { import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Button } from "@/components/ui/button"; -import { PersonaCategory } from "./interfaces"; +import { PersonaLabel } from "./interfaces"; import { PopupSpec } from "@/components/admin/connectors/Popup"; -interface CategoryCardProps { - category: PersonaCategory; - onUpdate: (id: number, name: string, description: string) => void; +interface LabelCardProps { + label: PersonaLabel; + onUpdate: (id: number, name: string) => void; onDelete: (id: number) => void; - refreshCategories: () => Promise; + refreshLabels: () => Promise; setPopup: (popup: PopupSpec) => void; } -export function CategoryCard({ - category, +export function LabelCard({ + label, onUpdate, onDelete, - refreshCategories, -}: CategoryCardProps) { + refreshLabels, +}: LabelCardProps) { const [isEditing, setIsEditing] = useState(false); - const [name, setName] = useState(category.name); - const [description, setDescription] = useState(category.description); + const [name, setName] = useState(label.name); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - await onUpdate(category.id, name, description); - await refreshCategories(); + await onUpdate(label.id, name); + await refreshLabels(); setIsEditing(false); }; const handleEdit = (e: React.MouseEvent) => { @@ -42,7 +41,7 @@ export function CategoryCard({ }; return ( - + {isEditing ? ( @@ -52,21 +51,10 @@ export function CategoryCard({ className="text-lg font-semibold" /> ) : ( - {category.name} + {label.name} )} - - {isEditing ? ( -