diff --git a/backend/onyx/db/persona.py b/backend/onyx/db/persona.py index 7bf9a1ab451..f5beb1d71e8 100644 --- a/backend/onyx/db/persona.py +++ b/backend/onyx/db/persona.py @@ -1,5 +1,6 @@ from collections.abc import Sequence from datetime import datetime +from enum import Enum from uuid import UUID from fastapi import HTTPException @@ -11,7 +12,6 @@ from sqlalchemy import update from sqlalchemy.orm import aliased from sqlalchemy.orm import joinedload -from sqlalchemy.orm import selectinload from sqlalchemy.orm import Session from onyx.auth.schemas import UserRole @@ -22,6 +22,7 @@ from onyx.configs.constants import NotificationType from onyx.context.search.enums import RecencyBiasSetting from onyx.db.constants import SLACK_BOT_PERSONA_PREFIX +from onyx.db.models import ConnectorCredentialPair from onyx.db.models import DocumentSet from onyx.db.models import Persona from onyx.db.models import Persona__User @@ -45,6 +46,12 @@ logger = setup_logger() +class PersonaLoadType(Enum): + NONE = "none" + MINIMAL = "minimal" + FULL = "full" + + def _add_user_filters( stmt: Select, user: User | None, get_editable: bool = True ) -> Select: @@ -322,6 +329,8 @@ def update_persona_public_status( def get_personas_for_user( + # defines how much of the persona to pre-load + load_type: PersonaLoadType, # if user is `None` assume the user is an admin or auth is disabled user: User | None, db_session: Session, @@ -329,9 +338,6 @@ def get_personas_for_user( include_default: bool = True, include_slack_bot_personas: bool = False, include_deleted: bool = False, - joinedload_all: bool = False, - # a bit jank - include_prompt: bool = True, ) -> Sequence[Persona]: stmt = select(Persona) stmt = _add_user_filters(stmt, user, get_editable) @@ -343,20 +349,45 @@ def get_personas_for_user( if not include_deleted: stmt = stmt.where(Persona.deleted.is_(False)) - if joinedload_all: + if load_type == PersonaLoadType.MINIMAL: + # For ChatPage, only load essential relationships + stmt = stmt.options( + # Used for retrieval capability checking + joinedload(Persona.tools), + # Used for filtering + joinedload(Persona.labels), + # only show document sets in the UI that the assistant has access to + joinedload(Persona.document_sets), + joinedload(Persona.document_sets) + .joinedload(DocumentSet.connector_credential_pairs) + .joinedload(ConnectorCredentialPair.connector), + joinedload(Persona.document_sets) + .joinedload(DocumentSet.connector_credential_pairs) + .joinedload(ConnectorCredentialPair.credential), + # user + joinedload(Persona.user), + ) + elif load_type == PersonaLoadType.FULL: stmt = stmt.options( - selectinload(Persona.tools), - selectinload(Persona.document_sets), - selectinload(Persona.groups), - selectinload(Persona.users), - selectinload(Persona.labels), - selectinload(Persona.user_files), - selectinload(Persona.user_folders), + joinedload(Persona.user), + joinedload(Persona.tools), + joinedload(Persona.document_sets) + .joinedload(DocumentSet.connector_credential_pairs) + .joinedload(ConnectorCredentialPair.connector), + joinedload(Persona.document_sets) + .joinedload(DocumentSet.connector_credential_pairs) + .joinedload(ConnectorCredentialPair.credential), + joinedload(Persona.document_sets).joinedload(DocumentSet.users), + joinedload(Persona.document_sets).joinedload(DocumentSet.groups), + joinedload(Persona.groups), + joinedload(Persona.users), + joinedload(Persona.labels), + joinedload(Persona.user_files), + joinedload(Persona.user_folders), + joinedload(Persona.prompts), ) - if include_prompt: - stmt = stmt.options(selectinload(Persona.prompts)) - results = db_session.execute(stmt).scalars().all() + results = db_session.execute(stmt).unique().scalars().all() return results diff --git a/backend/onyx/server/features/persona/api.py b/backend/onyx/server/features/persona/api.py index 4375a936473..86601603670 100644 --- a/backend/onyx/server/features/persona/api.py +++ b/backend/onyx/server/features/persona/api.py @@ -29,6 +29,7 @@ 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 PersonaLoadType from onyx.db.persona import update_all_personas_display_priority from onyx.db.persona import update_persona_is_default from onyx.db.persona import update_persona_label @@ -45,6 +46,7 @@ from onyx.server.features.persona.models import FullPersonaSnapshot from onyx.server.features.persona.models import GenerateStarterMessageRequest from onyx.server.features.persona.models import ImageGenerationToolStatus +from onyx.server.features.persona.models import MinimalPersonaSnapshot from onyx.server.features.persona.models import PersonaLabelCreate from onyx.server.features.persona.models import PersonaLabelResponse from onyx.server.features.persona.models import PersonaSharedNotificationData @@ -154,7 +156,7 @@ def list_personas_admin( user=user, get_editable=get_editable, include_deleted=include_deleted, - joinedload_all=True, + load_type=PersonaLoadType.FULL, ) ] @@ -393,14 +395,13 @@ def list_personas( db_session: Session = Depends(get_session), include_deleted: bool = False, persona_ids: list[int] = Query(None), -) -> list[PersonaSnapshot]: +) -> list[MinimalPersonaSnapshot]: personas = get_personas_for_user( + load_type=PersonaLoadType.MINIMAL, user=user, include_deleted=include_deleted, db_session=db_session, get_editable=False, - joinedload_all=True, - include_prompt=False, ) if persona_ids: @@ -416,7 +417,8 @@ def list_personas( ) ] - return [PersonaSnapshot.from_model(p) for p in personas] + result = [MinimalPersonaSnapshot.from_model(p) for p in personas] + return result @basic_router.get("/{persona_id}") diff --git a/backend/onyx/server/features/persona/models.py b/backend/onyx/server/features/persona/models.py index 7549cbc476c..a5e1aee8829 100644 --- a/backend/onyx/server/features/persona/models.py +++ b/backend/onyx/server/features/persona/models.py @@ -18,6 +18,64 @@ logger = setup_logger() +class MinimalPersonaSnapshot(BaseModel): + """Minimal persona model optimized for ChatPage.tsx - only includes fields actually used""" + + # Core fields used by ChatPage + id: int + name: str + description: str + tools: list[ToolSnapshot] + starter_messages: list[StarterMessage] | None + document_sets: list[DocumentSet] + llm_model_version_override: str | None + llm_model_provider_override: str | None + + uploaded_image_id: str | None + icon_shape: int | None + icon_color: str | None + + is_public: bool + is_visible: bool + display_priority: int | None + is_default_persona: bool + builtin_persona: bool + + labels: list["PersonaLabelSnapshot"] + owner: MinimalUserSnapshot | None + + @classmethod + def from_model(cls, persona: Persona) -> "MinimalPersonaSnapshot": + return MinimalPersonaSnapshot( + # Core fields actually used by ChatPage + id=persona.id, + name=persona.name, + description=persona.description, + tools=[ToolSnapshot.from_model(tool) for tool in persona.tools], + starter_messages=persona.starter_messages, + document_sets=[ + DocumentSet.from_model(document_set) + for document_set in persona.document_sets + ], + llm_model_version_override=persona.llm_model_version_override, + llm_model_provider_override=persona.llm_model_provider_override, + uploaded_image_id=persona.uploaded_image_id, + icon_shape=persona.icon_shape, + icon_color=persona.icon_color, + is_public=persona.is_public, + is_visible=persona.is_visible, + display_priority=persona.display_priority, + is_default_persona=persona.is_default_persona, + builtin_persona=persona.builtin_persona, + labels=[PersonaLabelSnapshot.from_model(label) for label in persona.labels], + owner=( + MinimalUserSnapshot(id=persona.user.id, email=persona.user.email) + if persona.user + else None + ), + ) + + class PromptSnapshot(BaseModel): id: int name: str diff --git a/backend/onyx/server/openai_assistants_api/asssistants_api.py b/backend/onyx/server/openai_assistants_api/asssistants_api.py index 97d182fb057..c1423f694ee 100644 --- a/backend/onyx/server/openai_assistants_api/asssistants_api.py +++ b/backend/onyx/server/openai_assistants_api/asssistants_api.py @@ -17,6 +17,7 @@ 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 PersonaLoadType from onyx.db.persona import upsert_persona from onyx.db.prompts import upsert_prompt from onyx.db.tools import get_tool_by_name @@ -244,10 +245,10 @@ def list_assistants( ) -> ListAssistantsResponse: personas = list( get_personas_for_user( + load_type=PersonaLoadType.FULL, user=user, db_session=db_session, get_editable=False, - joinedload_all=True, ) ) diff --git a/web/src/app/admin/assistants/AssistantEditor.tsx b/web/src/app/admin/assistants/AssistantEditor.tsx index 558982d58fd..641406c3ab2 100644 --- a/web/src/app/admin/assistants/AssistantEditor.tsx +++ b/web/src/app/admin/assistants/AssistantEditor.tsx @@ -258,7 +258,7 @@ export function AssistantEditor({ existingPersona?.llm_model_version_override ?? null, starter_messages: existingPersona?.starter_messages?.length ? existingPersona.starter_messages - : [{ message: "" }], + : [{ message: "", name: "" }], enabled_tools_map: enabledToolsMap, icon_color: existingPersona?.icon_color ?? defautIconColor, icon_shape: existingPersona?.icon_shape ?? defaultIconShape, @@ -526,10 +526,8 @@ export function AssistantEditor({ // to tell the backend to not fetch any documents const numChunks = searchToolEnabled ? values.num_chunks || 25 : 0; const starterMessages = values.starter_messages - .filter( - (message: { message: string }) => message.message.trim() !== "" - ) - .map((message: { message: string; name?: string }) => ({ + .filter((message: StarterMessage) => message.message.trim() !== "") + .map((message: StarterMessage) => ({ message: message.message, name: message.message, })); diff --git a/web/src/app/admin/assistants/PersonaTable.tsx b/web/src/app/admin/assistants/PersonaTable.tsx index 8f431b27a1a..57504f618f9 100644 --- a/web/src/app/admin/assistants/PersonaTable.tsx +++ b/web/src/app/admin/assistants/PersonaTable.tsx @@ -17,7 +17,6 @@ import { import { FiEdit2 } from "react-icons/fi"; import { TrashIcon } from "@/components/icons/icons"; import { useUser } from "@/components/user/UserProvider"; -import { useAssistants } from "@/components/context/AssistantsContext"; import { ConfirmEntityModal } from "@/components/modals/ConfirmEntityModal"; function PersonaTypeDisplay({ persona }: { persona: Persona }) { @@ -40,16 +39,18 @@ function PersonaTypeDisplay({ persona }: { persona: Persona }) { return Personal {persona.owner && <>({persona.owner.email})}; } -export function PersonasTable() { +export function PersonasTable({ + personas, + refreshPersonas, +}: { + personas: Persona[]; + refreshPersonas: () => void; +}) { const router = useRouter(); const { popup, setPopup } = usePopup(); const { refreshUser, isAdmin } = useUser(); - const { - allAssistants: assistants, - refreshAssistants, - editablePersonas, - } = useAssistants(); + const editablePersonas = personas.filter((p) => !p.builtin_persona); const editablePersonaIds = useMemo(() => { return new Set(editablePersonas.map((p) => p.id.toString())); }, [editablePersonas]); @@ -63,18 +64,18 @@ export function PersonasTable() { useEffect(() => { const editable = editablePersonas.sort(personaComparator); - const nonEditable = assistants + const nonEditable = personas .filter((p) => !editablePersonaIds.has(p.id.toString())) .sort(personaComparator); setFinalPersonas([...editable, ...nonEditable]); - }, [editablePersonas, assistants, editablePersonaIds]); + }, [editablePersonas, personas, editablePersonaIds]); const updatePersonaOrder = async (orderedPersonaIds: UniqueIdentifier[]) => { - const reorderedAssistants = orderedPersonaIds.map( - (id) => assistants.find((assistant) => assistant.id.toString() === id)! + const reorderedPersonas = orderedPersonaIds.map( + (id) => personas.find((persona) => persona.id.toString() === id)! ); - setFinalPersonas(reorderedAssistants); + setFinalPersonas(reorderedPersonas); const displayPriorityMap = new Map(); orderedPersonaIds.forEach((personaId, ind) => { @@ -96,12 +97,12 @@ export function PersonasTable() { type: "error", message: `Failed to update persona order - ${await response.text()}`, }); - setFinalPersonas(assistants); - await refreshAssistants(); + setFinalPersonas(personas); + await refreshPersonas(); return; } - await refreshAssistants(); + await refreshPersonas(); await refreshUser(); }; @@ -119,7 +120,7 @@ export function PersonasTable() { if (personaToDelete) { const response = await deletePersona(personaToDelete.id); if (response.ok) { - await refreshAssistants(); + refreshPersonas(); closeDeleteModal(); } else { setPopup({ @@ -147,7 +148,7 @@ export function PersonasTable() { personaToToggleDefault.is_default_persona ); if (response.ok) { - await refreshAssistants(); + refreshPersonas(); closeDefaultModal(); } else { setPopup({ @@ -267,7 +268,7 @@ export function PersonasTable() { persona.is_visible ); if (response.ok) { - await refreshAssistants(); + refreshPersonas(); } else { setPopup({ type: "error", diff --git a/web/src/app/admin/assistants/hooks.ts b/web/src/app/admin/assistants/hooks.ts new file mode 100644 index 00000000000..ee4c824db56 --- /dev/null +++ b/web/src/app/admin/assistants/hooks.ts @@ -0,0 +1,30 @@ +import useSWR from "swr"; +import { errorHandlingFetcher } from "@/lib/fetcher"; +import { buildApiPath } from "@/lib/urlBuilder"; +import { Persona } from "@/app/admin/assistants/interfaces"; + +interface UseAdminPersonasOptions { + includeDeleted?: boolean; + getEditable?: boolean; +} + +export const useAdminPersonas = (options?: UseAdminPersonasOptions) => { + const { includeDeleted = false, getEditable = false } = options || {}; + + const url = buildApiPath("/api/admin/persona", { + include_deleted: includeDeleted.toString(), + get_editable: getEditable.toString(), + }); + + const { data, error, isLoading, mutate } = useSWR( + url, + errorHandlingFetcher + ); + + return { + personas: data, + error, + isLoading, + refresh: mutate, + }; +}; diff --git a/web/src/app/admin/assistants/interfaces.ts b/web/src/app/admin/assistants/interfaces.ts index 77fd63dfa51..fe88e3a9449 100644 --- a/web/src/app/admin/assistants/interfaces.ts +++ b/web/src/app/admin/assistants/interfaces.ts @@ -18,29 +18,36 @@ export interface Prompt { datetime_aware: boolean; default_prompt: boolean; } -export interface Persona { + +export interface MinimalPersonaSnapshot { id: number; name: string; description: string; - is_public: boolean; - is_visible: boolean; + tools: ToolSnapshot[]; + starter_messages: StarterMessage[] | null; + document_sets: DocumentSet[]; + llm_model_version_override?: string; + llm_model_provider_override?: string; + + uploaded_image_id?: string; icon_shape?: number; icon_color?: string; - uploaded_image_id?: string; - user_file_ids: number[]; - user_folder_ids: number[]; + + is_public: boolean; + is_visible: boolean; display_priority: number | null; is_default_persona: boolean; builtin_persona: boolean; - starter_messages: StarterMessage[] | null; - tools: ToolSnapshot[]; + labels?: PersonaLabel[]; owner: MinimalUserSnapshot | null; +} + +export interface Persona extends MinimalPersonaSnapshot { + user_file_ids: number[]; + user_folder_ids: number[]; users: MinimalUserSnapshot[]; groups: number[]; - document_sets: DocumentSet[]; - llm_model_provider_override?: string; - llm_model_version_override?: string; num_chunks?: number; } diff --git a/web/src/app/admin/assistants/lib.ts b/web/src/app/admin/assistants/lib.ts index 2cfce111272..fed7d0f7d14 100644 --- a/web/src/app/admin/assistants/lib.ts +++ b/web/src/app/admin/assistants/lib.ts @@ -1,5 +1,5 @@ import { LLMProviderView } from "../configuration/llm/interfaces"; -import { Persona, StarterMessage } from "./interfaces"; +import { MinimalPersonaSnapshot, Persona, StarterMessage } from "./interfaces"; interface PersonaUpsertRequest { name: string; @@ -250,7 +250,10 @@ function closerToZeroNegativesFirstComparator(a: number, b: number) { return absA > absB ? 1 : -1; } -export function personaComparator(a: Persona, b: Persona) { +export function personaComparator( + a: MinimalPersonaSnapshot | Persona, + b: MinimalPersonaSnapshot | Persona +) { if (a.display_priority === null && b.display_priority === null) { return closerToZeroNegativesFirstComparator(a.id, b.id); } diff --git a/web/src/app/admin/assistants/page.tsx b/web/src/app/admin/assistants/page.tsx index fc667cbf3dd..9923522d08c 100644 --- a/web/src/app/admin/assistants/page.tsx +++ b/web/src/app/admin/assistants/page.tsx @@ -1,3 +1,5 @@ +"use client"; + import { PersonasTable } from "./PersonaTable"; import Text from "@/components/ui/text"; import Title from "@/components/ui/title"; @@ -6,11 +8,20 @@ import { AssistantsIcon } from "@/components/icons/icons"; import { AdminPageTitle } from "@/components/admin/Title"; import { SubLabel } from "@/components/Field"; import CreateButton from "@/components/ui/createButton"; -export default async function Page() { - return ( -
- } title="Assistants" /> +import { useAdminPersonas } from "./hooks"; +import { Persona } from "./interfaces"; +import { ThreeDotsLoader } from "@/components/Loading"; +import { ErrorCallout } from "@/components/ErrorCallout"; +function MainContent({ + personas, + refreshPersonas, +}: { + personas: Persona[]; + refreshPersonas: () => void; +}) { + return ( +
Assistants are a way to build custom search/question-answering experiences for different use cases. @@ -40,8 +51,35 @@ export default async function Page() { hidden will not be displayed. Editable assistants are shown at the top. - +
); } + +export default function Page() { + const { personas, isLoading, error, refresh } = useAdminPersonas(); + + return ( +
+ } title="Assistants" /> + + {isLoading && } + + {error && ( + + )} + + {!isLoading && !error && ( + + )} +
+ ); +} diff --git a/web/src/app/admin/bots/SlackBotTable.tsx b/web/src/app/admin/bots/SlackBotTable.tsx index ddab71ce08a..f8fe9674657 100644 --- a/web/src/app/admin/bots/SlackBotTable.tsx +++ b/web/src/app/admin/bots/SlackBotTable.tsx @@ -3,7 +3,6 @@ import { PageSelector } from "@/components/PageSelector"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; -import { FiEdit } from "react-icons/fi"; import { Table, TableBody, diff --git a/web/src/app/admin/bots/[bot-id]/channels/SlackChannelConfigCreationForm.tsx b/web/src/app/admin/bots/[bot-id]/channels/SlackChannelConfigCreationForm.tsx index d1826e955ed..5b4c2520246 100644 --- a/web/src/app/admin/bots/[bot-id]/channels/SlackChannelConfigCreationForm.tsx +++ b/web/src/app/admin/bots/[bot-id]/channels/SlackChannelConfigCreationForm.tsx @@ -16,7 +16,7 @@ import { } from "../lib"; import CardSection from "@/components/admin/CardSection"; import { useRouter } from "next/navigation"; -import { Persona } from "@/app/admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; import { StandardAnswerCategoryResponse } from "@/components/standardAnswers/getStandardAnswerCategoriesIfEE"; import { SEARCH_TOOL_ID } from "@/app/chat/tools/constants"; import { SlackChannelConfigFormFields } from "./SlackChannelConfigFormFields"; @@ -30,7 +30,7 @@ export const SlackChannelConfigCreationForm = ({ }: { slack_bot_id: number; documentSets: DocumentSet[]; - personas: Persona[]; + personas: MinimalPersonaSnapshot[]; standardAnswerCategoryResponse: StandardAnswerCategoryResponse; existingSlackChannelConfig?: SlackChannelConfig; }) => { @@ -59,7 +59,7 @@ export const SlackChannelConfigCreationForm = ({ } return acc; }, - [[], []] as [Persona[], Persona[]] + [[], []] as [MinimalPersonaSnapshot[], MinimalPersonaSnapshot[]] ); }, [personas]); diff --git a/web/src/app/admin/bots/[bot-id]/channels/SlackChannelConfigFormFields.tsx b/web/src/app/admin/bots/[bot-id]/channels/SlackChannelConfigFormFields.tsx index 33517637ec3..c0a0b21d1c3 100644 --- a/web/src/app/admin/bots/[bot-id]/channels/SlackChannelConfigFormFields.tsx +++ b/web/src/app/admin/bots/[bot-id]/channels/SlackChannelConfigFormFields.tsx @@ -11,7 +11,7 @@ import { TextFormField, } from "@/components/Field"; import { Button } from "@/components/ui/button"; -import { Persona } from "@/app/admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; import { DocumentSetSelectable } from "@/components/documentSet/DocumentSetSelectable"; import CollapsibleSection from "@/app/admin/assistants/CollapsibleSection"; import { StandardAnswerCategoryResponse } from "@/components/standardAnswers/getStandardAnswerCategoriesIfEE"; @@ -48,8 +48,8 @@ export interface SlackChannelConfigFormFieldsProps { isUpdate: boolean; isDefault: boolean; documentSets: DocumentSet[]; - searchEnabledAssistants: Persona[]; - nonSearchAssistants: Persona[]; + searchEnabledAssistants: MinimalPersonaSnapshot[]; + nonSearchAssistants: MinimalPersonaSnapshot[]; standardAnswerCategoryResponse: StandardAnswerCategoryResponse; setPopup: (popup: { message: string; @@ -82,8 +82,8 @@ export function SlackChannelConfigFormFields({ ); const [syncEnabledAssistants, availableAssistants] = useMemo(() => { - const sync: Persona[] = []; - const available: Persona[] = []; + const sync: MinimalPersonaSnapshot[] = []; + const available: MinimalPersonaSnapshot[] = []; searchEnabledAssistants.forEach((persona) => { const hasSyncSet = persona.document_sets.some(documentSetContainsSync); @@ -461,23 +461,25 @@ export function SlackChannelConfigFormFields({ Un-selectable assistants:

- {syncEnabledAssistants.map((persona: Persona) => ( - - ))} + {syncEnabledAssistants.map( + (persona: MinimalPersonaSnapshot) => ( + + ) + )}
)} diff --git a/web/src/app/admin/bots/[bot-id]/channels/new/page.tsx b/web/src/app/admin/bots/[bot-id]/channels/new/page.tsx index 7308c98e707..56f4848c7b9 100644 --- a/web/src/app/admin/bots/[bot-id]/channels/new/page.tsx +++ b/web/src/app/admin/bots/[bot-id]/channels/new/page.tsx @@ -5,12 +5,8 @@ import { ErrorCallout } from "@/components/ErrorCallout"; import { DocumentSet, ValidSources } from "@/lib/types"; import { BackButton } from "@/components/BackButton"; import { fetchAssistantsSS } from "@/lib/assistants/fetchAssistantsSS"; -import { - getStandardAnswerCategoriesIfEE, - StandardAnswerCategoryResponse, -} from "@/components/standardAnswers/getStandardAnswerCategoriesIfEE"; +import { getStandardAnswerCategoriesIfEE } from "@/components/standardAnswers/getStandardAnswerCategoriesIfEE"; import { redirect } from "next/navigation"; -import { Persona } from "../../../../assistants/interfaces"; import { SourceIcon } from "@/components/SourceIcon"; async function NewChannelConfigPage(props: { @@ -32,8 +28,8 @@ async function NewChannelConfigPage(props: { standardAnswerCategoryResponse, ] = await Promise.all([ fetchSS("/manage/document-set") as Promise, - fetchAssistantsSS() as Promise<[Persona[], string | null]>, - getStandardAnswerCategoriesIfEE() as Promise, + fetchAssistantsSS(), + getStandardAnswerCategoriesIfEE(), ]); if (!documentSetsResponse.ok) { diff --git a/web/src/app/admin/configuration/llm/LLMProviderUpdateForm.tsx b/web/src/app/admin/configuration/llm/LLMProviderUpdateForm.tsx index 94e9c37ccad..4da28d68ae0 100644 --- a/web/src/app/admin/configuration/llm/LLMProviderUpdateForm.tsx +++ b/web/src/app/admin/configuration/llm/LLMProviderUpdateForm.tsx @@ -16,7 +16,6 @@ import { useState } from "react"; import { useSWRConfig } from "swr"; import { LLMProviderView, - ModelConfiguration, ModelConfigurationUpsertRequest, WellKnownLLMProviderDescriptor, } from "./interfaces"; diff --git a/web/src/app/admin/connector/[ccPairId]/ReIndexModal.tsx b/web/src/app/admin/connector/[ccPairId]/ReIndexModal.tsx index c187551fc80..1b4e9b99920 100644 --- a/web/src/app/admin/connector/[ccPairId]/ReIndexModal.tsx +++ b/web/src/app/admin/connector/[ccPairId]/ReIndexModal.tsx @@ -2,7 +2,7 @@ import { Button } from "@/components/ui/button"; import { useState } from "react"; -import { usePopup, PopupSpec } from "@/components/admin/connectors/Popup"; +import { PopupSpec } from "@/components/admin/connectors/Popup"; import { triggerIndexing } from "./lib"; import { Modal } from "@/components/Modal"; import Text from "@/components/ui/text"; diff --git a/web/src/app/assistants/mine/AssistantCard.tsx b/web/src/app/assistants/mine/AssistantCard.tsx index 79456785236..92817fa70b3 100644 --- a/web/src/app/assistants/mine/AssistantCard.tsx +++ b/web/src/app/assistants/mine/AssistantCard.tsx @@ -15,7 +15,7 @@ import { PopoverContent, } from "@/components/ui/popover"; import { AssistantIcon } from "@/components/assistants/AssistantIcon"; -import { Persona } from "@/app/admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; import { useUser } from "@/components/user/UserProvider"; import { useAssistants } from "@/components/context/AssistantsContext"; import { checkUserOwnsAssistant } from "@/lib/assistants/utils"; @@ -54,7 +54,7 @@ export const AssistantBadge = ({ }; const AssistantCard: React.FC<{ - persona: Persona; + persona: MinimalPersonaSnapshot; pinned: boolean; closeModal: () => void; }> = ({ persona, pinned, closeModal }) => { diff --git a/web/src/app/auth/login/EmailPasswordForm.tsx b/web/src/app/auth/login/EmailPasswordForm.tsx index 3a9709ea491..4a773f9a212 100644 --- a/web/src/app/auth/login/EmailPasswordForm.tsx +++ b/web/src/app/auth/login/EmailPasswordForm.tsx @@ -9,7 +9,6 @@ import * as Yup from "yup"; import { requestEmailVerification } from "../lib"; import { useState } from "react"; import { Spinner } from "@/components/Spinner"; -import { NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED } from "@/lib/constants"; import Link from "next/link"; import { useUser } from "@/components/user/UserProvider"; import { useRouter } from "next/navigation"; diff --git a/web/src/app/chat/ChatIntro.tsx b/web/src/app/chat/ChatIntro.tsx index 7944b29fcd5..2a7e836059f 100644 --- a/web/src/app/chat/ChatIntro.tsx +++ b/web/src/app/chat/ChatIntro.tsx @@ -1,7 +1,11 @@ import { AssistantIcon } from "@/components/assistants/AssistantIcon"; -import { Persona } from "../admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "../admin/assistants/interfaces"; -export function ChatIntro({ selectedPersona }: { selectedPersona: Persona }) { +export function ChatIntro({ + selectedPersona, +}: { + selectedPersona: MinimalPersonaSnapshot; +}) { return (
diff --git a/web/src/app/chat/ChatPage.tsx b/web/src/app/chat/ChatPage.tsx index f71f60f18cd..12abd4b762e 100644 --- a/web/src/app/chat/ChatPage.tsx +++ b/web/src/app/chat/ChatPage.tsx @@ -29,7 +29,7 @@ import { import Prism from "prismjs"; import Cookies from "js-cookie"; import { HistorySidebar } from "./sessionSidebar/HistorySidebar"; -import { Persona } from "../admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "../admin/assistants/interfaces"; import { HealthCheckBanner } from "@/components/health/healthcheck"; import { buildChatUrl, @@ -49,7 +49,6 @@ import { setMessageAsLatest, updateLlmOverrideForChatSession, updateParentChildren, - uploadFilesForChat, useScrollonStream, } from "./lib"; import { @@ -405,7 +404,7 @@ export function ChatPage({ const existingChatSessionAssistantId = selectedChatSession?.persona_id; const [selectedAssistant, setSelectedAssistant] = useState< - Persona | undefined + MinimalPersonaSnapshot | undefined >( // NOTE: look through available assistants here, so that even if the user // has hidden this assistant it still shows the correct assistant when @@ -435,7 +434,7 @@ export function ChatPage({ }; const [alternativeAssistant, setAlternativeAssistant] = - useState(null); + useState(null); const [presentingDocument, setPresentingDocument] = useState(null); @@ -446,7 +445,7 @@ export function ChatPage({ // 3. First pinned assistants (ordered list of pinned assistants) // 4. Available assistants (ordered list of available assistants) // Relevant test: `live_assistant.spec.ts` - const liveAssistant: Persona | undefined = useMemo( + const liveAssistant: MinimalPersonaSnapshot | undefined = useMemo( () => alternativeAssistant || selectedAssistant || @@ -535,7 +534,7 @@ export function ChatPage({ // 2. we "@"ed the `GPT` assistant and sent a message // 3. while the `GPT` assistant message is generating, we "@" the `Paraphrase` assistant const [alternativeGeneratingAssistant, setAlternativeGeneratingAssistant] = - useState(null); + useState(null); // used to track whether or not the initial "submit on load" has been performed // this only applies if `?submit-on-load=true` or `?submit-on-load=1` is in the URL @@ -1327,7 +1326,7 @@ export function ChatPage({ queryOverride?: string; forceSearch?: boolean; isSeededChat?: boolean; - alternativeAssistantOverride?: Persona | null; + alternativeAssistantOverride?: MinimalPersonaSnapshot | null; modelOverride?: LlmDescriptor; regenerationRequest?: RegenerationRequest | null; overrideFileDescriptors?: FileDescriptor[]; @@ -2197,10 +2196,7 @@ export function ChatPage({ useEffect(() => { if (liveAssistant) { const hasSearchTool = liveAssistant.tools.some( - (tool) => - tool.in_code_tool_id === SEARCH_TOOL_ID && - liveAssistant.user_file_ids?.length == 0 && - liveAssistant.user_folder_ids?.length == 0 + (tool) => tool.in_code_tool_id === SEARCH_TOOL_ID ); setRetrievalEnabled(hasSearchTool); if (!hasSearchTool) { @@ -2212,10 +2208,7 @@ export function ChatPage({ const [retrievalEnabled, setRetrievalEnabled] = useState(() => { if (liveAssistant) { return liveAssistant.tools.some( - (tool) => - tool.in_code_tool_id === SEARCH_TOOL_ID && - liveAssistant.user_file_ids?.length == 0 && - liveAssistant.user_folder_ids?.length == 0 + (tool) => tool.in_code_tool_id === SEARCH_TOOL_ID ); } return false; diff --git a/web/src/app/chat/RegenerateOption.tsx b/web/src/app/chat/RegenerateOption.tsx index 8446e15a9cf..36b1972e70e 100644 --- a/web/src/app/chat/RegenerateOption.tsx +++ b/web/src/app/chat/RegenerateOption.tsx @@ -5,7 +5,7 @@ import { useLlmManager, } from "@/lib/hooks"; -import { Persona } from "@/app/admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; import { parseLlmDescriptor } from "@/lib/llm/utils"; import { useState } from "react"; import { Hoverable } from "@/components/Hoverable"; @@ -19,7 +19,7 @@ export default function RegenerateOption({ overriddenModel, onDropdownVisibleChange, }: { - selectedAssistant: Persona; + selectedAssistant: MinimalPersonaSnapshot; regenerate: (modelOverRide: LlmDescriptor) => Promise; overriddenModel?: string; onDropdownVisibleChange: (isVisible: boolean) => void; diff --git a/web/src/app/chat/input/ChatInputBar.tsx b/web/src/app/chat/input/ChatInputBar.tsx index a2c04905ccf..f0cdd63a3d5 100644 --- a/web/src/app/chat/input/ChatInputBar.tsx +++ b/web/src/app/chat/input/ChatInputBar.tsx @@ -2,7 +2,7 @@ import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; import { FiPlusCircle, FiPlus, FiX, FiFilter } from "react-icons/fi"; import { FiLoader } from "react-icons/fi"; import { ChatInputOption } from "./ChatInputOption"; -import { Persona } from "@/app/admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; import LLMPopover from "./LLMPopover"; import { InputPrompt } from "@/app/chat/interfaces"; @@ -39,7 +39,6 @@ import { AgenticToggle } from "./AgenticToggle"; import { SettingsContext } from "@/components/settings/SettingsProvider"; import { getProviderIcon } from "@/app/admin/configuration/llm/utils"; import { useDocumentsContext } from "../my-documents/DocumentsContext"; -import { UploadIntent } from "../ChatPage"; const MAX_INPUT_HEIGHT = 200; export const SourceChip2 = ({ @@ -182,10 +181,12 @@ interface ChatInputBarProps { onSubmit: () => void; llmManager: LlmManager; chatState: ChatState; - alternativeAssistant: Persona | null; + alternativeAssistant: MinimalPersonaSnapshot | null; // assistants - selectedAssistant: Persona; - setAlternativeAssistant: (alternativeAssistant: Persona | null) => void; + selectedAssistant: MinimalPersonaSnapshot; + setAlternativeAssistant: ( + alternativeAssistant: MinimalPersonaSnapshot | null + ) => void; toggleDocumentSidebar: () => void; setFiles: (files: FileDescriptor[]) => void; handleFileUpload: (files: File[]) => void; @@ -306,7 +307,7 @@ export function ChatInputBar({ }; }, []); - const updatedTaggedAssistant = (assistant: Persona) => { + const updatedTaggedAssistant = (assistant: MinimalPersonaSnapshot) => { setAlternativeAssistant( assistant.id == selectedAssistant.id ? null : assistant ); diff --git a/web/src/app/chat/input/LLMPopover.tsx b/web/src/app/chat/input/LLMPopover.tsx index f5e6df3947a..6d64a2fec25 100644 --- a/web/src/app/chat/input/LLMPopover.tsx +++ b/web/src/app/chat/input/LLMPopover.tsx @@ -8,7 +8,7 @@ import { getDisplayNameForModel, LlmDescriptor } from "@/lib/hooks"; import { modelSupportsImageInput } from "@/lib/llm/utils"; import { LLMProviderDescriptor } from "@/app/admin/configuration/llm/interfaces"; import { getProviderIcon } from "@/app/admin/configuration/llm/utils"; -import { Persona } from "@/app/admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; import { LlmManager } from "@/lib/hooks"; import { @@ -28,7 +28,7 @@ interface LLMPopoverProps { llmProviders: LLMProviderDescriptor[]; llmManager: LlmManager; requiresImageGeneration?: boolean; - currentAssistant?: Persona; + currentAssistant?: MinimalPersonaSnapshot; trigger?: React.ReactElement; onSelect?: (value: string) => void; currentModelName?: string; diff --git a/web/src/app/chat/lib.tsx b/web/src/app/chat/lib.tsx index 6e89f56eef2..3f4243d3ec9 100644 --- a/web/src/app/chat/lib.tsx +++ b/web/src/app/chat/lib.tsx @@ -28,7 +28,7 @@ import { AgenticMessageResponseIDInfo, UserKnowledgeFilePacket, } from "./interfaces"; -import { Persona } from "../admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "../admin/assistants/interfaces"; import { ReadonlyURLSearchParams } from "next/navigation"; import { SEARCH_PARAM_NAMES } from "./searchParams"; import { Settings } from "../admin/settings/interfaces"; @@ -631,8 +631,8 @@ export function removeMessage( export function checkAnyAssistantHasSearch( messageHistory: Message[], - availableAssistants: Persona[], - livePersona: Persona + availableAssistants: MinimalPersonaSnapshot[], + livePersona: MinimalPersonaSnapshot ): boolean { const response = messageHistory.some((message) => { @@ -653,19 +653,17 @@ export function checkAnyAssistantHasSearch( return response; } -export function personaIncludesRetrieval(selectedPersona: Persona) { +export function personaIncludesRetrieval( + selectedPersona: MinimalPersonaSnapshot +) { return selectedPersona.tools.some( (tool) => tool.in_code_tool_id && - [SEARCH_TOOL_ID, INTERNET_SEARCH_TOOL_ID].includes( - tool.in_code_tool_id - ) && - selectedPersona.user_file_ids?.length === 0 && - selectedPersona.user_folder_ids?.length === 0 + [SEARCH_TOOL_ID, INTERNET_SEARCH_TOOL_ID].includes(tool.in_code_tool_id) ); } -export function personaIncludesImage(selectedPersona: Persona) { +export function personaIncludesImage(selectedPersona: MinimalPersonaSnapshot) { return selectedPersona.tools.some( (tool) => tool.in_code_tool_id && tool.in_code_tool_id == IIMAGE_GENERATION_TOOL_ID diff --git a/web/src/app/chat/message/AgenticMessage.tsx b/web/src/app/chat/message/AgenticMessage.tsx index 86cc80f4214..4ac364774e9 100644 --- a/web/src/app/chat/message/AgenticMessage.tsx +++ b/web/src/app/chat/message/AgenticMessage.tsx @@ -27,7 +27,7 @@ import rehypePrism from "rehype-prism-plus"; import "prismjs/themes/prism-tomorrow.css"; import "./custom-code-styles.css"; -import { Persona } from "@/app/admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; import { AssistantIcon } from "@/components/assistants/AssistantIcon"; import { LikeFeedback, DislikeFeedback } from "@/components/icons/icons"; @@ -107,8 +107,8 @@ export const AgenticMessage = ({ onMessageSelection?: (messageId: number) => void; toggleDocumentSelection?: (second: boolean) => void; docs?: OnyxDocument[] | null; - alternativeAssistant?: Persona | null; - currentPersona: Persona; + alternativeAssistant?: MinimalPersonaSnapshot | null; + currentPersona: MinimalPersonaSnapshot; messageId: number | null; content: string | JSX.Element; files?: FileDescriptor[]; diff --git a/web/src/app/chat/message/Messages.tsx b/web/src/app/chat/message/Messages.tsx index d76890362e6..4762ef98ae0 100644 --- a/web/src/app/chat/message/Messages.tsx +++ b/web/src/app/chat/message/Messages.tsx @@ -40,7 +40,7 @@ import { CodeBlock } from "./CodeBlock"; import rehypePrism from "rehype-prism-plus"; import "prismjs/themes/prism-tomorrow.css"; import "./custom-code-styles.css"; -import { Persona } from "@/app/admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; import { AssistantIcon } from "@/components/assistants/AssistantIcon"; import { LikeFeedback, DislikeFeedback } from "@/components/icons/icons"; import { @@ -254,8 +254,8 @@ export const AIMessage = ({ selectedDocuments?: OnyxDocument[] | null; toggleDocumentSelection?: () => void; docs?: OnyxDocument[] | null; - alternativeAssistant?: Persona | null; - currentPersona: Persona; + alternativeAssistant?: MinimalPersonaSnapshot | null; + currentPersona: MinimalPersonaSnapshot; messageId: number | null; content: string | JSX.Element; files?: FileDescriptor[]; diff --git a/web/src/app/chat/modal/configuration/AssistantsTab.tsx b/web/src/app/chat/modal/configuration/AssistantsTab.tsx index a29db23cdec..1cdfdbf474c 100644 --- a/web/src/app/chat/modal/configuration/AssistantsTab.tsx +++ b/web/src/app/chat/modal/configuration/AssistantsTab.tsx @@ -13,7 +13,7 @@ import { sortableKeyboardCoordinates, verticalListSortingStrategy, } from "@dnd-kit/sortable"; -import { Persona } from "@/app/admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; import { LLMProviderDescriptor } from "@/app/admin/configuration/llm/interfaces"; import { getFinalLLM } from "@/lib/llm/utils"; import React, { useEffect, useState } from "react"; @@ -27,9 +27,9 @@ export function AssistantsTab({ llmProviders, onSelect, }: { - selectedAssistant: Persona; + selectedAssistant: MinimalPersonaSnapshot; llmProviders: LLMProviderDescriptor[]; - onSelect: (assistant: Persona) => void; + onSelect: (assistant: MinimalPersonaSnapshot) => void; }) { const { refreshUser } = useUser(); const [_, llmName] = getFinalLLM(llmProviders, null, null); diff --git a/web/src/app/chat/my-documents/[id]/UserFolderContent.tsx b/web/src/app/chat/my-documents/[id]/UserFolderContent.tsx index 9ac07f3eb6f..c8458393682 100644 --- a/web/src/app/chat/my-documents/[id]/UserFolderContent.tsx +++ b/web/src/app/chat/my-documents/[id]/UserFolderContent.tsx @@ -6,7 +6,6 @@ import { ArrowUp, ArrowDown, Trash, - Upload, } from "lucide-react"; import { useDocumentsContext } from "../DocumentsContext"; import { useChatContext } from "@/components/context/ChatContext"; diff --git a/web/src/app/chat/my-documents/components/FilePicker.tsx b/web/src/app/chat/my-documents/components/FilePicker.tsx index e6e3e84f255..3341b3fc5b7 100644 --- a/web/src/app/chat/my-documents/components/FilePicker.tsx +++ b/web/src/app/chat/my-documents/components/FilePicker.tsx @@ -34,7 +34,6 @@ import { TooltipTrigger, TooltipContent, } from "@/components/ui/tooltip"; -import { useRouter } from "next/navigation"; import { usePopup } from "@/components/admin/connectors/Popup"; import { getFormattedDateTime } from "@/lib/dateUtils"; import { FileUploadSection } from "../[id]/components/upload/FileUploadSection"; diff --git a/web/src/app/chat/my-documents/components/SelectedItemsList.tsx b/web/src/app/chat/my-documents/components/SelectedItemsList.tsx index 41844f8cf44..1a3b71ec99a 100644 --- a/web/src/app/chat/my-documents/components/SelectedItemsList.tsx +++ b/web/src/app/chat/my-documents/components/SelectedItemsList.tsx @@ -2,7 +2,6 @@ import React from "react"; import { cn, truncateString } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { X, FolderIcon, Loader2 } from "lucide-react"; -import { ScrollArea } from "@/components/ui/scroll-area"; import { FolderResponse, FileResponse } from "../DocumentsContext"; import { getFileIconFromFileNameAndLink } from "@/lib/assistantIconUtils"; import { MinimalOnyxDocument } from "@/lib/search/interfaces"; diff --git a/web/src/app/chat/sessionSidebar/HistorySidebar.tsx b/web/src/app/chat/sessionSidebar/HistorySidebar.tsx index 09c8421ff43..0905a68bdb5 100644 --- a/web/src/app/chat/sessionSidebar/HistorySidebar.tsx +++ b/web/src/app/chat/sessionSidebar/HistorySidebar.tsx @@ -27,7 +27,7 @@ import { import { PagesTab } from "./PagesTab"; import { pageType } from "./types"; import LogoWithText from "@/components/header/LogoWithText"; -import { Persona } from "@/app/admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; import { DragEndEvent } from "@dnd-kit/core"; import { useAssistants } from "@/components/context/AssistantsContext"; import { AssistantIcon } from "@/components/assistants/AssistantIcon"; @@ -56,7 +56,7 @@ import { restrictToVerticalAxis } from "@dnd-kit/modifiers"; import { TruncatedText } from "@/components/ui/truncatedText"; interface HistorySidebarProps { - liveAssistant?: Persona | null; + liveAssistant?: MinimalPersonaSnapshot | null; page: pageType; existingChats?: ChatSession[]; currentChatSession?: ChatSession | null | undefined; @@ -73,7 +73,7 @@ interface HistorySidebarProps { } interface SortableAssistantProps { - assistant: Persona; + assistant: MinimalPersonaSnapshot; active: boolean; onClick: () => void; onPinAction: (e: React.MouseEvent) => void; @@ -213,18 +213,22 @@ export const HistorySidebar = forwardRef( const { active, over } = event; if (active.id !== over?.id) { - setPinnedAssistants((prevAssistants: Persona[]) => { + setPinnedAssistants((prevAssistants: MinimalPersonaSnapshot[]) => { const oldIndex = prevAssistants.findIndex( - (a: Persona) => (a.id === 0 ? "assistant-0" : a.id) === active.id + (a: MinimalPersonaSnapshot) => + (a.id === 0 ? "assistant-0" : a.id) === active.id ); const newIndex = prevAssistants.findIndex( - (a: Persona) => (a.id === 0 ? "assistant-0" : a.id) === over?.id + (a: MinimalPersonaSnapshot) => + (a.id === 0 ? "assistant-0" : a.id) === over?.id ); const newOrder = arrayMove(prevAssistants, oldIndex, newIndex); // Ensure we're sending the correct IDs to the API - const reorderedIds = newOrder.map((a: Persona) => a.id); + const reorderedIds = newOrder.map( + (a: MinimalPersonaSnapshot) => a.id + ); reorderPinnedAssistants(reorderedIds); return newOrder; @@ -351,7 +355,7 @@ export const HistorySidebar = forwardRef( strategy={verticalListSortingStrategy} >
- {pinnedAssistants.map((assistant: Persona) => ( + {pinnedAssistants.map((assistant: MinimalPersonaSnapshot) => ( void; + onSelect: (assistant: MinimalPersonaSnapshot) => void; }) => { const renderBadgeContent = (tool: { name: string }) => { switch (tool.name) { @@ -73,9 +73,9 @@ export const AssistantCard = ({ }; export function DraggableAssistantCard(props: { - assistant: Persona; + assistant: MinimalPersonaSnapshot; isSelected: boolean; - onSelect: (assistant: Persona) => void; + onSelect: (assistant: MinimalPersonaSnapshot) => void; llmName: string; }) { const { diff --git a/web/src/components/assistants/AssistantIcon.tsx b/web/src/components/assistants/AssistantIcon.tsx index 65696fe5cfe..c5c5abe887c 100644 --- a/web/src/components/assistants/AssistantIcon.tsx +++ b/web/src/components/assistants/AssistantIcon.tsx @@ -1,6 +1,9 @@ import React from "react"; import crypto from "crypto"; -import { Persona } from "@/app/admin/assistants/interfaces"; +import { + MinimalPersonaSnapshot, + Persona, +} from "@/app/admin/assistants/interfaces"; import { buildImgUrl } from "@/app/chat/files/images/utils"; import { ArtAsistantIcon, @@ -92,7 +95,7 @@ export function AssistantIcon({ className, disableToolip, }: { - assistant: Persona; + assistant: MinimalPersonaSnapshot | Persona; size?: IconSize; className?: string; border?: boolean; diff --git a/web/src/components/assistants/StarterMessage.tsx b/web/src/components/assistants/StarterMessage.tsx index fa0da24f3b9..b1924b8edc3 100644 --- a/web/src/components/assistants/StarterMessage.tsx +++ b/web/src/components/assistants/StarterMessage.tsx @@ -1,12 +1,12 @@ import { useContext } from "react"; -import { Persona } from "../../app/admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "../../app/admin/assistants/interfaces"; import { SettingsContext } from "../settings/SettingsProvider"; export function StarterMessages({ currentPersona, onSubmit, }: { - currentPersona: Persona; + currentPersona: MinimalPersonaSnapshot; onSubmit: (messageOverride: string) => void; }) { const settings = useContext(SettingsContext); diff --git a/web/src/components/context/AppProvider.tsx b/web/src/components/context/AppProvider.tsx index 4b22c548a27..8e693336a9d 100644 --- a/web/src/components/context/AppProvider.tsx +++ b/web/src/components/context/AppProvider.tsx @@ -4,7 +4,7 @@ import { UserProvider } from "../user/UserProvider"; import { ProviderContextProvider } from "../chat/ProviderContext"; import { SettingsProvider } from "../settings/SettingsProvider"; import { AssistantsProvider } from "./AssistantsContext"; -import { Persona } from "@/app/admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; import { User } from "@/lib/types"; import { ModalProvider } from "./ModalContext"; import { AuthTypeMetadata } from "@/lib/userSS"; @@ -13,7 +13,7 @@ interface AppProviderProps { children: React.ReactNode; user: User | null; settings: CombinedSettings; - assistants: Persona[]; + assistants: MinimalPersonaSnapshot[]; hasAnyConnectors: boolean; hasImageCompatibleModel: boolean; authTypeMetadata: AuthTypeMetadata; diff --git a/web/src/components/context/AssistantsContext.tsx b/web/src/components/context/AssistantsContext.tsx index 3223788454b..950f6e7ba75 100644 --- a/web/src/components/context/AssistantsContext.tsx +++ b/web/src/components/context/AssistantsContext.tsx @@ -8,7 +8,7 @@ import React, { SetStateAction, Dispatch, } from "react"; -import { Persona } from "@/app/admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; import { classifyAssistants, orderAssistantsForUser, @@ -18,18 +18,18 @@ import { import { useUser } from "../user/UserProvider"; interface AssistantsContextProps { - assistants: Persona[]; - visibleAssistants: Persona[]; - hiddenAssistants: Persona[]; - finalAssistants: Persona[]; - ownedButHiddenAssistants: Persona[]; + assistants: MinimalPersonaSnapshot[]; + visibleAssistants: MinimalPersonaSnapshot[]; + hiddenAssistants: MinimalPersonaSnapshot[]; + finalAssistants: MinimalPersonaSnapshot[]; + ownedButHiddenAssistants: MinimalPersonaSnapshot[]; refreshAssistants: () => Promise; isImageGenerationAvailable: boolean; // Admin only - editablePersonas: Persona[]; - allAssistants: Persona[]; - pinnedAssistants: Persona[]; - setPinnedAssistants: Dispatch>; + editablePersonas: MinimalPersonaSnapshot[]; + allAssistants: MinimalPersonaSnapshot[]; + pinnedAssistants: MinimalPersonaSnapshot[]; + setPinnedAssistants: Dispatch>; } const AssistantsContext = createContext( @@ -38,27 +38,36 @@ const AssistantsContext = createContext( export const AssistantsProvider: React.FC<{ children: React.ReactNode; - initialAssistants: Persona[]; - hasAnyConnectors: boolean; - hasImageCompatibleModel: boolean; + initialAssistants: MinimalPersonaSnapshot[]; + hasAnyConnectors?: boolean; + hasImageCompatibleModel?: boolean; }> = ({ children, initialAssistants, hasAnyConnectors, hasImageCompatibleModel, }) => { - const [assistants, setAssistants] = useState( + const [assistants, setAssistants] = useState( initialAssistants || [] ); const { user, isAdmin, isCurator } = useUser(); - const [editablePersonas, setEditablePersonas] = useState([]); - const [allAssistants, setAllAssistants] = useState([]); + const [editablePersonas, setEditablePersonas] = useState< + MinimalPersonaSnapshot[] + >([]); + const [allAssistants, setAllAssistants] = useState( + [] + ); - const [pinnedAssistants, setPinnedAssistants] = useState(() => { + const [pinnedAssistants, setPinnedAssistants] = useState< + MinimalPersonaSnapshot[] + >(() => { if (user?.preferences.pinned_assistants) { return user.preferences.pinned_assistants .map((id) => assistants.find((assistant) => assistant.id === id)) - .filter((assistant): assistant is Persona => assistant !== undefined); + .filter( + (assistant): assistant is MinimalPersonaSnapshot => + assistant !== undefined + ); } else { return assistants.filter((a) => a.is_default_persona); } @@ -69,7 +78,10 @@ export const AssistantsProvider: React.FC<{ if (user?.preferences.pinned_assistants) { return user.preferences.pinned_assistants .map((id) => assistants.find((assistant) => assistant.id === id)) - .filter((assistant): assistant is Persona => assistant !== undefined); + .filter( + (assistant): assistant is MinimalPersonaSnapshot => + assistant !== undefined + ); } else { return assistants.filter((a) => a.is_default_persona); } @@ -135,13 +147,9 @@ export const AssistantsProvider: React.FC<{ }, }); if (!response.ok) throw new Error("Failed to fetch assistants"); - let assistants: Persona[] = await response.json(); + let assistants: MinimalPersonaSnapshot[] = await response.json(); - let filteredAssistants = filterAssistants( - assistants, - hasAnyConnectors, - hasImageCompatibleModel - ); + let filteredAssistants = filterAssistants(assistants); setAssistants(filteredAssistants); diff --git a/web/src/components/credentials/actions/CreateCredential.tsx b/web/src/components/credentials/actions/CreateCredential.tsx index 88331acf202..17bfdec7f26 100644 --- a/web/src/components/credentials/actions/CreateCredential.tsx +++ b/web/src/components/credentials/actions/CreateCredential.tsx @@ -3,17 +3,13 @@ import { Button } from "@/components/ui/button"; import { ValidSources, AccessType } from "@/lib/types"; import { FaAccusoft } from "react-icons/fa"; import { submitCredential } from "@/components/admin/connectors/CredentialForm"; -import { BooleanFormField, TextFormField } from "@/components/Field"; +import { TextFormField } from "@/components/Field"; import { Form, Formik, FormikHelpers } from "formik"; import { PopupSpec } from "@/components/admin/connectors/Popup"; import { getSourceDocLink } from "@/lib/sources"; import GDriveMain from "@/app/admin/connectors/[connector]/pages/gdrive/GoogleDrivePage"; import { Connector } from "@/lib/connectors/connectors"; -import { - Credential, - credentialTemplates, - getDisplayNameForCredentialKey, -} from "@/lib/connectors/credentials"; +import { Credential, credentialTemplates } from "@/lib/connectors/credentials"; import { PlusCircleIcon } from "../../icons/icons"; import { GmailMain } from "@/app/admin/connectors/[connector]/pages/gmail/GmailPage"; import { ActionType, dictionaryType } from "../types"; diff --git a/web/src/lib/assistants/checkOwnership.ts b/web/src/lib/assistants/checkOwnership.ts index 0102f406349..115ffc86c96 100644 --- a/web/src/lib/assistants/checkOwnership.ts +++ b/web/src/lib/assistants/checkOwnership.ts @@ -1,14 +1,20 @@ -import { Persona } from "@/app/admin/assistants/interfaces"; +import { + MinimalPersonaSnapshot, + Persona, +} from "@/app/admin/assistants/interfaces"; import { User } from "../types"; import { checkUserIsNoAuthUser } from "../user"; -export function checkUserOwnsAssistant(user: User | null, assistant: Persona) { +export function checkUserOwnsAssistant( + user: User | null, + assistant: MinimalPersonaSnapshot | Persona +) { return checkUserIdOwnsAssistant(user?.id, assistant); } export function checkUserIdOwnsAssistant( userId: string | undefined, - assistant: Persona + assistant: MinimalPersonaSnapshot | Persona ) { return ( (!userId || diff --git a/web/src/lib/assistants/fetchAssistantsSS.ts b/web/src/lib/assistants/fetchAssistantsSS.ts index abe96eb2cc5..7fc3e6c7217 100644 --- a/web/src/lib/assistants/fetchAssistantsSS.ts +++ b/web/src/lib/assistants/fetchAssistantsSS.ts @@ -1,12 +1,12 @@ -import { Persona } from "@/app/admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; import { fetchSS } from "../utilsSS"; -export type FetchAssistantsResponse = [Persona[], string | null]; +export type FetchAssistantsResponse = [MinimalPersonaSnapshot[], string | null]; export async function fetchAssistantsSS(): Promise { const response = await fetchSS("/persona"); if (response.ok) { - return [(await response.json()) as Persona[], null]; + return [(await response.json()) as MinimalPersonaSnapshot[], null]; } return [[], (await response.json()).detail || "Unknown Error"]; } diff --git a/web/src/lib/assistants/utils.ts b/web/src/lib/assistants/utils.ts index 5c3ab5adcd1..106b8c3c862 100644 --- a/web/src/lib/assistants/utils.ts +++ b/web/src/lib/assistants/utils.ts @@ -1,15 +1,18 @@ -import { Persona } from "@/app/admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; import { User } from "../types"; import { checkUserIsNoAuthUser } from "../user"; import { personaComparator } from "@/app/admin/assistants/lib"; -export function checkUserOwnsAssistant(user: User | null, assistant: Persona) { +export function checkUserOwnsAssistant( + user: User | null, + assistant: MinimalPersonaSnapshot +) { return checkUserIdOwnsAssistant(user?.id, assistant); } export function checkUserIdOwnsAssistant( userId: string | undefined, - assistant: Persona + assistant: MinimalPersonaSnapshot ) { return ( (!userId || @@ -19,7 +22,10 @@ export function checkUserIdOwnsAssistant( ); } -export function classifyAssistants(user: User | null, assistants: Persona[]) { +export function classifyAssistants( + user: User | null, + assistants: MinimalPersonaSnapshot[] +) { if (!user) { return { visibleAssistants: assistants.filter( @@ -59,7 +65,7 @@ export function classifyAssistants(user: User | null, assistants: Persona[]) { } export function orderAssistantsForUser( - assistants: Persona[], + assistants: MinimalPersonaSnapshot[], user: User | null ) { let orderedAssistants = [...assistants]; @@ -112,7 +118,7 @@ export function orderAssistantsForUser( export function getUserCreatedAssistants( user: User | null, - assistants: Persona[] + assistants: MinimalPersonaSnapshot[] ) { return assistants.filter((assistant) => checkUserOwnsAssistant(user, assistant) @@ -121,29 +127,10 @@ export function getUserCreatedAssistants( // Filter assistants based on connector status, image compatibility and visibility export function filterAssistants( - assistants: Persona[], - hasAnyConnectors: boolean, - hasImageCompatibleModel: boolean -): Persona[] { + assistants: MinimalPersonaSnapshot[] +): MinimalPersonaSnapshot[] { let filteredAssistants = assistants.filter( (assistant) => assistant.is_visible ); - - if (!hasAnyConnectors) { - filteredAssistants = filteredAssistants.filter( - (assistant) => - assistant.num_chunks === 0 || assistant.document_sets.length > 0 - ); - } - - if (!hasImageCompatibleModel) { - filteredAssistants = filteredAssistants.filter( - (assistant) => - !assistant.tools.some( - (tool) => tool.in_code_tool_id === "ImageGenerationTool" - ) - ); - } - return filteredAssistants.sort(personaComparator); } diff --git a/web/src/lib/chat/fetchAssistantdata.ts b/web/src/lib/chat/fetchAssistantdata.ts index 048dc357c70..f17b70de593 100644 --- a/web/src/lib/chat/fetchAssistantdata.ts +++ b/web/src/lib/chat/fetchAssistantdata.ts @@ -1,12 +1,12 @@ import { fetchSS } from "@/lib/utilsSS"; -import { Persona } from "@/app/admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; import { fetchLLMProvidersSS } from "@/lib/llm/fetchLLMs"; import { fetchAssistantsSS } from "../assistants/fetchAssistantsSS"; import { modelSupportsImageInput } from "../llm/utils"; import { filterAssistants } from "../assistants/utils"; interface AssistantData { - assistants: Persona[]; + assistants: MinimalPersonaSnapshot[]; hasAnyConnectors: boolean; hasImageCompatibleModel: boolean; } @@ -51,11 +51,7 @@ export async function fetchAssistantData(): Promise { ) ); - let filteredAssistants = filterAssistants( - assistants, - hasAnyConnectors, - hasImageCompatibleModel - ); + let filteredAssistants = filterAssistants(assistants); return { assistants: filteredAssistants, diff --git a/web/src/lib/hooks.ts b/web/src/lib/hooks.ts index 23343fbe856..b37bb50a689 100644 --- a/web/src/lib/hooks.ts +++ b/web/src/lib/hooks.ts @@ -18,7 +18,10 @@ import { ChatSession } from "@/app/chat/interfaces"; import { AllUsersResponse } from "./types"; import { Credential } from "./connectors/credentials"; import { SettingsContext } from "@/components/settings/SettingsProvider"; -import { Persona, PersonaLabel } from "@/app/admin/assistants/interfaces"; +import { + MinimalPersonaSnapshot, + PersonaLabel, +} from "@/app/admin/assistants/interfaces"; import { LLMProviderDescriptor } from "@/app/admin/configuration/llm/interfaces"; import { isAnthropic } from "@/app/admin/configuration/llm/utils"; import { getSourceMetadata } from "./sources"; @@ -380,7 +383,7 @@ export interface LlmManager { updateModelOverrideBasedOnChatSession: (chatSession?: ChatSession) => void; imageFilesPresent: boolean; updateImageFilesPresent: (present: boolean) => void; - liveAssistant: Persona | null; + liveAssistant: MinimalPersonaSnapshot | null; maxTemperature: number; } @@ -428,7 +431,7 @@ providing appropriate defaults for new conversations based on the available tool export function useLlmManager( llmProviders: LLMProviderDescriptor[], currentChatSession?: ChatSession, - liveAssistant?: Persona + liveAssistant?: MinimalPersonaSnapshot ): LlmManager { const { user } = useUser(); diff --git a/web/src/lib/llm/utils.ts b/web/src/lib/llm/utils.ts index 47c01b8ee93..ea7249f7ae4 100644 --- a/web/src/lib/llm/utils.ts +++ b/web/src/lib/llm/utils.ts @@ -1,4 +1,4 @@ -import { Persona } from "@/app/admin/assistants/interfaces"; +import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces"; import { LLMProviderDescriptor, ModelConfiguration, @@ -7,7 +7,7 @@ import { LlmDescriptor } from "@/lib/hooks"; export function getFinalLLM( llmProviders: LLMProviderDescriptor[], - persona: Persona | null, + persona: MinimalPersonaSnapshot | null, currentLlm: LlmDescriptor | null ): [string, string] { const defaultProvider = llmProviders.find( @@ -38,7 +38,7 @@ export function getFinalLLM( } export function getLLMProviderOverrideForPersona( - liveAssistant: Persona, + liveAssistant: MinimalPersonaSnapshot, llmProviders: LLMProviderDescriptor[] ): LlmDescriptor | null { const overrideProvider = liveAssistant.llm_model_provider_override;