Compare commits

...

1 Commits

Author SHA1 Message Date
Weves
2b16c3e80b Revert "Reduce amount of stuff we fetch on /persona (#4988)"
This reverts commit adf48de652.
2025-07-14 09:07:21 -07:00
47 changed files with 250 additions and 408 deletions

View File

@@ -1,6 +1,5 @@
from collections.abc import Sequence
from datetime import datetime
from enum import Enum
from uuid import UUID
from fastapi import HTTPException
@@ -12,6 +11,7 @@ from sqlalchemy import select
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,7 +22,6 @@ from onyx.configs.chat_configs import CONTEXT_CHUNKS_BELOW
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
@@ -46,12 +45,6 @@ from onyx.utils.variable_functionality import fetch_versioned_implementation
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:
@@ -329,8 +322,6 @@ 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,
@@ -338,6 +329,9 @@ 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)
@@ -349,45 +343,20 @@ def get_personas_for_user(
if not include_deleted:
stmt = stmt.where(Persona.deleted.is_(False))
if load_type == PersonaLoadType.MINIMAL:
# For ChatPage, only load essential relationships
if joinedload_all:
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(
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),
selectinload(Persona.tools),
selectinload(Persona.document_sets),
selectinload(Persona.groups),
selectinload(Persona.users),
selectinload(Persona.labels),
selectinload(Persona.user_files),
selectinload(Persona.user_folders),
)
if include_prompt:
stmt = stmt.options(selectinload(Persona.prompts))
results = db_session.execute(stmt).unique().scalars().all()
results = db_session.execute(stmt).scalars().all()
return results

View File

@@ -29,7 +29,6 @@ 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 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
@@ -46,7 +45,6 @@ from onyx.secondary_llm_flows.starter_message_creation import (
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
@@ -156,7 +154,7 @@ def list_personas_admin(
user=user,
get_editable=get_editable,
include_deleted=include_deleted,
load_type=PersonaLoadType.FULL,
joinedload_all=True,
)
]
@@ -395,13 +393,14 @@ def list_personas(
db_session: Session = Depends(get_session),
include_deleted: bool = False,
persona_ids: list[int] = Query(None),
) -> list[MinimalPersonaSnapshot]:
) -> list[PersonaSnapshot]:
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:
@@ -417,8 +416,7 @@ def list_personas(
)
]
result = [MinimalPersonaSnapshot.from_model(p) for p in personas]
return result
return [PersonaSnapshot.from_model(p) for p in personas]
@basic_router.get("/{persona_id}")

View File

@@ -18,64 +18,6 @@ from onyx.utils.logger import setup_logger
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

View File

@@ -17,7 +17,6 @@ from onyx.db.models import User
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
@@ -245,10 +244,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,
)
)

View File

@@ -258,7 +258,7 @@ export function AssistantEditor({
existingPersona?.llm_model_version_override ?? null,
starter_messages: existingPersona?.starter_messages?.length
? existingPersona.starter_messages
: [{ message: "", name: "" }],
: [{ message: "" }],
enabled_tools_map: enabledToolsMap,
icon_color: existingPersona?.icon_color ?? defautIconColor,
icon_shape: existingPersona?.icon_shape ?? defaultIconShape,
@@ -526,8 +526,10 @@ 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: StarterMessage) => message.message.trim() !== "")
.map((message: StarterMessage) => ({
.filter(
(message: { message: string }) => message.message.trim() !== ""
)
.map((message: { message: string; name?: string }) => ({
message: message.message,
name: message.message,
}));

View File

@@ -17,6 +17,7 @@ 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 }) {
@@ -39,18 +40,16 @@ function PersonaTypeDisplay({ persona }: { persona: Persona }) {
return <Text>Personal {persona.owner && <>({persona.owner.email})</>}</Text>;
}
export function PersonasTable({
personas,
refreshPersonas,
}: {
personas: Persona[];
refreshPersonas: () => void;
}) {
export function PersonasTable() {
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]);
@@ -64,18 +63,18 @@ export function PersonasTable({
useEffect(() => {
const editable = editablePersonas.sort(personaComparator);
const nonEditable = personas
const nonEditable = assistants
.filter((p) => !editablePersonaIds.has(p.id.toString()))
.sort(personaComparator);
setFinalPersonas([...editable, ...nonEditable]);
}, [editablePersonas, personas, editablePersonaIds]);
}, [editablePersonas, assistants, editablePersonaIds]);
const updatePersonaOrder = async (orderedPersonaIds: UniqueIdentifier[]) => {
const reorderedPersonas = orderedPersonaIds.map(
(id) => personas.find((persona) => persona.id.toString() === id)!
const reorderedAssistants = orderedPersonaIds.map(
(id) => assistants.find((assistant) => assistant.id.toString() === id)!
);
setFinalPersonas(reorderedPersonas);
setFinalPersonas(reorderedAssistants);
const displayPriorityMap = new Map<UniqueIdentifier, number>();
orderedPersonaIds.forEach((personaId, ind) => {
@@ -97,12 +96,12 @@ export function PersonasTable({
type: "error",
message: `Failed to update persona order - ${await response.text()}`,
});
setFinalPersonas(personas);
await refreshPersonas();
setFinalPersonas(assistants);
await refreshAssistants();
return;
}
await refreshPersonas();
await refreshAssistants();
await refreshUser();
};
@@ -120,7 +119,7 @@ export function PersonasTable({
if (personaToDelete) {
const response = await deletePersona(personaToDelete.id);
if (response.ok) {
refreshPersonas();
await refreshAssistants();
closeDeleteModal();
} else {
setPopup({
@@ -148,7 +147,7 @@ export function PersonasTable({
personaToToggleDefault.is_default_persona
);
if (response.ok) {
refreshPersonas();
await refreshAssistants();
closeDefaultModal();
} else {
setPopup({
@@ -268,7 +267,7 @@ export function PersonasTable({
persona.is_visible
);
if (response.ok) {
refreshPersonas();
await refreshAssistants();
} else {
setPopup({
type: "error",

View File

@@ -1,30 +0,0 @@
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<Persona[]>(
url,
errorHandlingFetcher
);
return {
personas: data,
error,
isLoading,
refresh: mutate,
};
};

View File

@@ -18,36 +18,29 @@ export interface Prompt {
datetime_aware: boolean;
default_prompt: boolean;
}
export interface MinimalPersonaSnapshot {
export interface Persona {
id: number;
name: string;
description: string;
tools: ToolSnapshot[];
starter_messages: StarterMessage[] | null;
document_sets: DocumentSetSummary[];
llm_model_version_override?: string;
llm_model_provider_override?: string;
uploaded_image_id?: string;
icon_shape?: number;
icon_color?: string;
is_public: boolean;
is_visible: boolean;
icon_shape?: number;
icon_color?: string;
uploaded_image_id?: string;
user_file_ids: number[];
user_folder_ids: number[];
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: DocumentSetSummary[];
llm_model_provider_override?: string;
llm_model_version_override?: string;
num_chunks?: number;
}

View File

@@ -1,5 +1,5 @@
import { LLMProviderView } from "../configuration/llm/interfaces";
import { MinimalPersonaSnapshot, Persona, StarterMessage } from "./interfaces";
import { Persona, StarterMessage } from "./interfaces";
interface PersonaUpsertRequest {
name: string;
@@ -250,10 +250,7 @@ function closerToZeroNegativesFirstComparator(a: number, b: number) {
return absA > absB ? 1 : -1;
}
export function personaComparator(
a: MinimalPersonaSnapshot | Persona,
b: MinimalPersonaSnapshot | Persona
) {
export function personaComparator(a: Persona, b: Persona) {
if (a.display_priority === null && b.display_priority === null) {
return closerToZeroNegativesFirstComparator(a.id, b.id);
}

View File

@@ -1,5 +1,3 @@
"use client";
import { PersonasTable } from "./PersonaTable";
import Text from "@/components/ui/text";
import Title from "@/components/ui/title";
@@ -8,20 +6,11 @@ import { AssistantsIcon } from "@/components/icons/icons";
import { AdminPageTitle } from "@/components/admin/Title";
import { SubLabel } from "@/components/Field";
import CreateButton from "@/components/ui/createButton";
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;
}) {
export default async function Page() {
return (
<div>
<div className="mx-auto container">
<AdminPageTitle icon={<AssistantsIcon size={32} />} title="Assistants" />
<Text className="mb-2">
Assistants are a way to build custom search/question-answering
experiences for different use cases.
@@ -51,35 +40,8 @@ function MainContent({
hidden will not be displayed. Editable assistants are shown at the
top.
</SubLabel>
<PersonasTable personas={personas} refreshPersonas={refreshPersonas} />
<PersonasTable />
</div>
</div>
);
}
export default function Page() {
const { personas, isLoading, error, refresh } = useAdminPersonas();
return (
<div className="mx-auto container">
<AdminPageTitle icon={<AssistantsIcon size={32} />} title="Assistants" />
{isLoading && <ThreeDotsLoader />}
{error && (
<ErrorCallout
errorTitle="Failed to load assistants"
errorMsg={
error?.info?.message ||
error?.info?.detail ||
"An unknown error occurred"
}
/>
)}
{!isLoading && !error && (
<MainContent personas={personas || []} refreshPersonas={refresh} />
)}
</div>
);
}

View File

@@ -3,6 +3,7 @@
import { PageSelector } from "@/components/PageSelector";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
import { FiEdit } from "react-icons/fi";
import {
Table,
TableBody,

View File

@@ -16,7 +16,7 @@ import {
} from "../lib";
import CardSection from "@/components/admin/CardSection";
import { useRouter } from "next/navigation";
import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces";
import { Persona } 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: DocumentSetSummary[];
personas: MinimalPersonaSnapshot[];
personas: Persona[];
standardAnswerCategoryResponse: StandardAnswerCategoryResponse;
existingSlackChannelConfig?: SlackChannelConfig;
}) => {
@@ -59,7 +59,7 @@ export const SlackChannelConfigCreationForm = ({
}
return acc;
},
[[], []] as [MinimalPersonaSnapshot[], MinimalPersonaSnapshot[]]
[[], []] as [Persona[], Persona[]]
);
}, [personas]);

View File

@@ -11,7 +11,7 @@ import {
TextFormField,
} from "@/components/Field";
import { Button } from "@/components/ui/button";
import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces";
import { Persona } from "@/app/admin/assistants/interfaces";
import { DocumentSetSelectable } from "@/components/documentSet/DocumentSetSelectable";
import CollapsibleSection from "@/app/admin/assistants/CollapsibleSection";
import { StandardAnswerCategoryResponse } from "@/components/standardAnswers/getStandardAnswerCategoriesIfEE";
@@ -44,8 +44,8 @@ export interface SlackChannelConfigFormFieldsProps {
isUpdate: boolean;
isDefault: boolean;
documentSets: DocumentSetSummary[];
searchEnabledAssistants: MinimalPersonaSnapshot[];
nonSearchAssistants: MinimalPersonaSnapshot[];
searchEnabledAssistants: Persona[];
nonSearchAssistants: Persona[];
standardAnswerCategoryResponse: StandardAnswerCategoryResponse;
setPopup: (popup: {
message: string;
@@ -92,8 +92,8 @@ export function SlackChannelConfigFormFields({
};
const [syncEnabledAssistants, availableAssistants] = useMemo(() => {
const sync: MinimalPersonaSnapshot[] = [];
const available: MinimalPersonaSnapshot[] = [];
const sync: Persona[] = [];
const available: Persona[] = [];
searchEnabledAssistants.forEach((persona) => {
const hasSyncSet = persona.document_sets.some(documentSetContainsSync);
@@ -380,25 +380,23 @@ export function SlackChannelConfigFormFields({
Un-selectable assistants:
</p>
<div className="mb-3 mt-2 flex gap-2 flex-wrap text-sm">
{syncEnabledAssistants.map(
(persona: MinimalPersonaSnapshot) => (
<button
type="button"
onClick={() =>
router.push(`/admin/assistants/${persona.id}`)
}
key={persona.id}
className="p-2 bg-background-100 cursor-pointer rounded-md flex items-center gap-2"
>
<AssistantIcon
assistant={persona}
size={16}
className="flex-none"
/>
{persona.name}
</button>
)
)}
{syncEnabledAssistants.map((persona: Persona) => (
<button
type="button"
onClick={() =>
router.push(`/admin/assistants/${persona.id}`)
}
key={persona.id}
className="p-2 bg-background-100 cursor-pointer rounded-md flex items-center gap-2"
>
<AssistantIcon
assistant={persona}
size={16}
className="flex-none"
/>
{persona.name}
</button>
))}
</div>
</div>
)}

View File

@@ -5,8 +5,12 @@ import { ErrorCallout } from "@/components/ErrorCallout";
import { DocumentSetSummary, ValidSources } from "@/lib/types";
import { BackButton } from "@/components/BackButton";
import { fetchAssistantsSS } from "@/lib/assistants/fetchAssistantsSS";
import { getStandardAnswerCategoriesIfEE } from "@/components/standardAnswers/getStandardAnswerCategoriesIfEE";
import {
getStandardAnswerCategoriesIfEE,
StandardAnswerCategoryResponse,
} from "@/components/standardAnswers/getStandardAnswerCategoriesIfEE";
import { redirect } from "next/navigation";
import { Persona } from "../../../../assistants/interfaces";
import { SourceIcon } from "@/components/SourceIcon";
async function NewChannelConfigPage(props: {
@@ -28,8 +32,8 @@ async function NewChannelConfigPage(props: {
standardAnswerCategoryResponse,
] = await Promise.all([
fetchSS("/manage/document-set") as Promise<Response>,
fetchAssistantsSS(),
getStandardAnswerCategoriesIfEE(),
fetchAssistantsSS() as Promise<[Persona[], string | null]>,
getStandardAnswerCategoriesIfEE() as Promise<StandardAnswerCategoryResponse>,
]);
if (!documentSetsResponse.ok) {

View File

@@ -2,7 +2,7 @@
import { Button } from "@/components/ui/button";
import { useState } from "react";
import { PopupSpec } from "@/components/admin/connectors/Popup";
import { usePopup, PopupSpec } from "@/components/admin/connectors/Popup";
import { triggerIndexing } from "./lib";
import { Modal } from "@/components/Modal";
import Text from "@/components/ui/text";

View File

@@ -15,7 +15,7 @@ import {
PopoverContent,
} from "@/components/ui/popover";
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces";
import { Persona } 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: MinimalPersonaSnapshot;
persona: Persona;
pinned: boolean;
closeModal: () => void;
}> = ({ persona, pinned, closeModal }) => {

View File

@@ -9,6 +9,7 @@ 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";

View File

@@ -1,11 +1,7 @@
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
import { MinimalPersonaSnapshot } from "../admin/assistants/interfaces";
import { Persona } from "../admin/assistants/interfaces";
export function ChatIntro({
selectedPersona,
}: {
selectedPersona: MinimalPersonaSnapshot;
}) {
export function ChatIntro({ selectedPersona }: { selectedPersona: Persona }) {
return (
<div data-testid="chat-intro" className="flex flex-col items-center gap-6">
<div className="relative flex flex-col gap-y-4 w-fit mx-auto justify-center">

View File

@@ -29,7 +29,7 @@ import {
import Prism from "prismjs";
import Cookies from "js-cookie";
import { HistorySidebar } from "./sessionSidebar/HistorySidebar";
import { MinimalPersonaSnapshot } from "../admin/assistants/interfaces";
import { Persona } from "../admin/assistants/interfaces";
import { HealthCheckBanner } from "@/components/health/healthcheck";
import {
buildChatUrl,
@@ -49,6 +49,7 @@ import {
setMessageAsLatest,
updateLlmOverrideForChatSession,
updateParentChildren,
uploadFilesForChat,
useScrollonStream,
} from "./lib";
import {
@@ -404,7 +405,7 @@ export function ChatPage({
const existingChatSessionAssistantId = selectedChatSession?.persona_id;
const [selectedAssistant, setSelectedAssistant] = useState<
MinimalPersonaSnapshot | undefined
Persona | undefined
>(
// NOTE: look through available assistants here, so that even if the user
// has hidden this assistant it still shows the correct assistant when
@@ -434,7 +435,7 @@ export function ChatPage({
};
const [alternativeAssistant, setAlternativeAssistant] =
useState<MinimalPersonaSnapshot | null>(null);
useState<Persona | null>(null);
const [presentingDocument, setPresentingDocument] =
useState<MinimalOnyxDocument | null>(null);
@@ -445,7 +446,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: MinimalPersonaSnapshot | undefined = useMemo(
const liveAssistant: Persona | undefined = useMemo(
() =>
alternativeAssistant ||
selectedAssistant ||
@@ -534,7 +535,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<MinimalPersonaSnapshot | null>(null);
useState<Persona | null>(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
@@ -1326,7 +1327,7 @@ export function ChatPage({
queryOverride?: string;
forceSearch?: boolean;
isSeededChat?: boolean;
alternativeAssistantOverride?: MinimalPersonaSnapshot | null;
alternativeAssistantOverride?: Persona | null;
modelOverride?: LlmDescriptor;
regenerationRequest?: RegenerationRequest | null;
overrideFileDescriptors?: FileDescriptor[];
@@ -2196,7 +2197,10 @@ export function ChatPage({
useEffect(() => {
if (liveAssistant) {
const hasSearchTool = liveAssistant.tools.some(
(tool) => tool.in_code_tool_id === SEARCH_TOOL_ID
(tool) =>
tool.in_code_tool_id === SEARCH_TOOL_ID &&
liveAssistant.user_file_ids?.length == 0 &&
liveAssistant.user_folder_ids?.length == 0
);
setRetrievalEnabled(hasSearchTool);
if (!hasSearchTool) {
@@ -2208,7 +2212,10 @@ export function ChatPage({
const [retrievalEnabled, setRetrievalEnabled] = useState(() => {
if (liveAssistant) {
return liveAssistant.tools.some(
(tool) => tool.in_code_tool_id === SEARCH_TOOL_ID
(tool) =>
tool.in_code_tool_id === SEARCH_TOOL_ID &&
liveAssistant.user_file_ids?.length == 0 &&
liveAssistant.user_folder_ids?.length == 0
);
}
return false;

View File

@@ -5,7 +5,7 @@ import {
useLlmManager,
} from "@/lib/hooks";
import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces";
import { Persona } 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: MinimalPersonaSnapshot;
selectedAssistant: Persona;
regenerate: (modelOverRide: LlmDescriptor) => Promise<void>;
overriddenModel?: string;
onDropdownVisibleChange: (isVisible: boolean) => void;

View File

@@ -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 { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces";
import { Persona } from "@/app/admin/assistants/interfaces";
import LLMPopover from "./LLMPopover";
import { InputPrompt } from "@/app/chat/interfaces";
@@ -39,6 +39,7 @@ 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 = ({
@@ -181,12 +182,10 @@ interface ChatInputBarProps {
onSubmit: () => void;
llmManager: LlmManager;
chatState: ChatState;
alternativeAssistant: MinimalPersonaSnapshot | null;
alternativeAssistant: Persona | null;
// assistants
selectedAssistant: MinimalPersonaSnapshot;
setAlternativeAssistant: (
alternativeAssistant: MinimalPersonaSnapshot | null
) => void;
selectedAssistant: Persona;
setAlternativeAssistant: (alternativeAssistant: Persona | null) => void;
toggleDocumentSidebar: () => void;
setFiles: (files: FileDescriptor[]) => void;
handleFileUpload: (files: File[]) => void;
@@ -307,7 +306,7 @@ export function ChatInputBar({
};
}, []);
const updatedTaggedAssistant = (assistant: MinimalPersonaSnapshot) => {
const updatedTaggedAssistant = (assistant: Persona) => {
setAlternativeAssistant(
assistant.id == selectedAssistant.id ? null : assistant
);

View File

@@ -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 { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces";
import { Persona } from "@/app/admin/assistants/interfaces";
import { LlmManager } from "@/lib/hooks";
import {
@@ -28,7 +28,7 @@ interface LLMPopoverProps {
llmProviders: LLMProviderDescriptor[];
llmManager: LlmManager;
requiresImageGeneration?: boolean;
currentAssistant?: MinimalPersonaSnapshot;
currentAssistant?: Persona;
trigger?: React.ReactElement;
onSelect?: (value: string) => void;
currentModelName?: string;

View File

@@ -28,7 +28,7 @@ import {
AgenticMessageResponseIDInfo,
UserKnowledgeFilePacket,
} from "./interfaces";
import { MinimalPersonaSnapshot } from "../admin/assistants/interfaces";
import { Persona } 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: MinimalPersonaSnapshot[],
livePersona: MinimalPersonaSnapshot
availableAssistants: Persona[],
livePersona: Persona
): boolean {
const response =
messageHistory.some((message) => {
@@ -653,17 +653,19 @@ export function checkAnyAssistantHasSearch(
return response;
}
export function personaIncludesRetrieval(
selectedPersona: MinimalPersonaSnapshot
) {
export function personaIncludesRetrieval(selectedPersona: Persona) {
return selectedPersona.tools.some(
(tool) =>
tool.in_code_tool_id &&
[SEARCH_TOOL_ID, INTERNET_SEARCH_TOOL_ID].includes(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
);
}
export function personaIncludesImage(selectedPersona: MinimalPersonaSnapshot) {
export function personaIncludesImage(selectedPersona: Persona) {
return selectedPersona.tools.some(
(tool) =>
tool.in_code_tool_id && tool.in_code_tool_id == IIMAGE_GENERATION_TOOL_ID

View File

@@ -27,7 +27,7 @@ import rehypePrism from "rehype-prism-plus";
import "prismjs/themes/prism-tomorrow.css";
import "./custom-code-styles.css";
import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces";
import { Persona } 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?: MinimalPersonaSnapshot | null;
currentPersona: MinimalPersonaSnapshot;
alternativeAssistant?: Persona | null;
currentPersona: Persona;
messageId: number | null;
content: string | JSX.Element;
files?: FileDescriptor[];

View File

@@ -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 { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces";
import { Persona } 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?: MinimalPersonaSnapshot | null;
currentPersona: MinimalPersonaSnapshot;
alternativeAssistant?: Persona | null;
currentPersona: Persona;
messageId: number | null;
content: string | JSX.Element;
files?: FileDescriptor[];

View File

@@ -13,7 +13,7 @@ import {
sortableKeyboardCoordinates,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces";
import { Persona } 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: MinimalPersonaSnapshot;
selectedAssistant: Persona;
llmProviders: LLMProviderDescriptor[];
onSelect: (assistant: MinimalPersonaSnapshot) => void;
onSelect: (assistant: Persona) => void;
}) {
const { refreshUser } = useUser();
const [_, llmName] = getFinalLLM(llmProviders, null, null);

View File

@@ -6,6 +6,7 @@ import {
ArrowUp,
ArrowDown,
Trash,
Upload,
} from "lucide-react";
import { useDocumentsContext } from "../DocumentsContext";
import { useChatContext } from "@/components/context/ChatContext";

View File

@@ -34,6 +34,7 @@ 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";

View File

@@ -2,6 +2,7 @@ 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";

View File

@@ -27,7 +27,7 @@ import {
import { PagesTab } from "./PagesTab";
import { pageType } from "./types";
import LogoWithText from "@/components/header/LogoWithText";
import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces";
import { Persona } 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?: MinimalPersonaSnapshot | null;
liveAssistant?: Persona | null;
page: pageType;
existingChats?: ChatSession[];
currentChatSession?: ChatSession | null | undefined;
@@ -73,7 +73,7 @@ interface HistorySidebarProps {
}
interface SortableAssistantProps {
assistant: MinimalPersonaSnapshot;
assistant: Persona;
active: boolean;
onClick: () => void;
onPinAction: (e: React.MouseEvent) => void;
@@ -213,22 +213,18 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
const { active, over } = event;
if (active.id !== over?.id) {
setPinnedAssistants((prevAssistants: MinimalPersonaSnapshot[]) => {
setPinnedAssistants((prevAssistants: Persona[]) => {
const oldIndex = prevAssistants.findIndex(
(a: MinimalPersonaSnapshot) =>
(a.id === 0 ? "assistant-0" : a.id) === active.id
(a: Persona) => (a.id === 0 ? "assistant-0" : a.id) === active.id
);
const newIndex = prevAssistants.findIndex(
(a: MinimalPersonaSnapshot) =>
(a.id === 0 ? "assistant-0" : a.id) === over?.id
(a: Persona) => (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: MinimalPersonaSnapshot) => a.id
);
const reorderedIds = newOrder.map((a: Persona) => a.id);
reorderPinnedAssistants(reorderedIds);
return newOrder;
@@ -355,7 +351,7 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
strategy={verticalListSortingStrategy}
>
<div className="flex px-0 mr-4 flex-col gap-y-1 mt-1">
{pinnedAssistants.map((assistant: MinimalPersonaSnapshot) => (
{pinnedAssistants.map((assistant: Persona) => (
<SortableAssistant
key={assistant.id === 0 ? "assistant-0" : assistant.id}
assistant={assistant}

View File

@@ -15,12 +15,14 @@ import { useContext, useEffect, useState } from "react";
import { SettingsContext } from "@/components/settings/SettingsProvider";
import { OnyxInitializingLoader } from "@/components/OnyxInitializingLoader";
import { Persona } from "@/app/admin/assistants/interfaces";
import { Button } from "@/components/ui/button";
import { MinimalOnyxDocument } from "@/lib/search/interfaces";
import TextView from "@/components/chat/TextView";
import { DocumentResults } from "../../documentSidebar/DocumentResults";
import { Modal } from "@/components/Modal";
import FunctionalHeader from "@/components/chat/Header";
import FixedLogo from "@/components/logo/FixedLogo";
import { useRouter } from "next/navigation";
import Link from "next/link";
function BackToOnyxButton({

View File

@@ -25,6 +25,7 @@ import { deleteStandardAnswer } from "./lib";
import { FilterDropdown } from "@/components/search/filtering/FilterDropdown";
import { FiTag } from "react-icons/fi";
import { PageSelector } from "@/components/PageSelector";
import { CustomCheckbox } from "@/components/CustomCheckbox";
import Text from "@/components/ui/text";
import { TableHeader } from "@/components/ui/table";
import CreateButton from "@/components/ui/createButton";

View File

@@ -35,7 +35,7 @@ import { CheckedState } from "@radix-ui/react-checkbox";
import { transformLinkUri } from "@/lib/utils";
import FileInput from "@/app/admin/connectors/[connector]/pages/ConnectorInput/FileInput";
import { DatePicker } from "./ui/datePicker";
import { DatePicker, DatePickerProps } from "./ui/datePicker";
import { Textarea, TextareaProps } from "./ui/textarea";
export function SectionHeader({

View File

@@ -1,5 +1,3 @@
"use client";
import React, { useState, useEffect } from "react";
import "./loading.css";
import { ThreeDots } from "react-loader-spinner";

View File

@@ -40,6 +40,8 @@ import {
} from "@/app/admin/settings/interfaces";
import Link from "next/link";
import { Button } from "../ui/button";
import useSWR from "swr";
import { errorHandlingFetcher } from "@/lib/fetcher";
import { useIsKGExposed } from "@/app/admin/kg/utils";
import { useFederatedOAuthStatus } from "@/lib/hooks/useFederatedOAuthStatus";

View File

@@ -1,4 +1,4 @@
import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces";
import { Persona } from "@/app/admin/assistants/interfaces";
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
import { useSortable } from "@dnd-kit/sortable";
import React from "react";
@@ -14,9 +14,9 @@ export const AssistantCard = ({
isSelected,
onSelect,
}: {
assistant: MinimalPersonaSnapshot;
assistant: Persona;
isSelected: boolean;
onSelect: (assistant: MinimalPersonaSnapshot) => void;
onSelect: (assistant: Persona) => void;
}) => {
const renderBadgeContent = (tool: { name: string }) => {
switch (tool.name) {
@@ -73,9 +73,9 @@ export const AssistantCard = ({
};
export function DraggableAssistantCard(props: {
assistant: MinimalPersonaSnapshot;
assistant: Persona;
isSelected: boolean;
onSelect: (assistant: MinimalPersonaSnapshot) => void;
onSelect: (assistant: Persona) => void;
llmName: string;
}) {
const {

View File

@@ -1,9 +1,6 @@
import React from "react";
import crypto from "crypto";
import {
MinimalPersonaSnapshot,
Persona,
} from "@/app/admin/assistants/interfaces";
import { Persona } from "@/app/admin/assistants/interfaces";
import { buildImgUrl } from "@/app/chat/files/images/utils";
import {
ArtAsistantIcon,
@@ -95,7 +92,7 @@ export function AssistantIcon({
className,
disableToolip,
}: {
assistant: MinimalPersonaSnapshot | Persona;
assistant: Persona;
size?: IconSize;
className?: string;
border?: boolean;

View File

@@ -1,12 +1,12 @@
import { useContext } from "react";
import { MinimalPersonaSnapshot } from "../../app/admin/assistants/interfaces";
import { Persona } from "../../app/admin/assistants/interfaces";
import { SettingsContext } from "../settings/SettingsProvider";
export function StarterMessages({
currentPersona,
onSubmit,
}: {
currentPersona: MinimalPersonaSnapshot;
currentPersona: Persona;
onSubmit: (messageOverride: string) => void;
}) {
const settings = useContext(SettingsContext);

View File

@@ -4,7 +4,7 @@ import { UserProvider } from "../user/UserProvider";
import { ProviderContextProvider } from "../chat/ProviderContext";
import { SettingsProvider } from "../settings/SettingsProvider";
import { AssistantsProvider } from "./AssistantsContext";
import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces";
import { Persona } 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: MinimalPersonaSnapshot[];
assistants: Persona[];
hasAnyConnectors: boolean;
hasImageCompatibleModel: boolean;
authTypeMetadata: AuthTypeMetadata;

View File

@@ -8,7 +8,7 @@ import React, {
SetStateAction,
Dispatch,
} from "react";
import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces";
import { Persona } from "@/app/admin/assistants/interfaces";
import {
classifyAssistants,
orderAssistantsForUser,
@@ -18,18 +18,18 @@ import {
import { useUser } from "../user/UserProvider";
interface AssistantsContextProps {
assistants: MinimalPersonaSnapshot[];
visibleAssistants: MinimalPersonaSnapshot[];
hiddenAssistants: MinimalPersonaSnapshot[];
finalAssistants: MinimalPersonaSnapshot[];
ownedButHiddenAssistants: MinimalPersonaSnapshot[];
assistants: Persona[];
visibleAssistants: Persona[];
hiddenAssistants: Persona[];
finalAssistants: Persona[];
ownedButHiddenAssistants: Persona[];
refreshAssistants: () => Promise<void>;
isImageGenerationAvailable: boolean;
// Admin only
editablePersonas: MinimalPersonaSnapshot[];
allAssistants: MinimalPersonaSnapshot[];
pinnedAssistants: MinimalPersonaSnapshot[];
setPinnedAssistants: Dispatch<SetStateAction<MinimalPersonaSnapshot[]>>;
editablePersonas: Persona[];
allAssistants: Persona[];
pinnedAssistants: Persona[];
setPinnedAssistants: Dispatch<SetStateAction<Persona[]>>;
}
const AssistantsContext = createContext<AssistantsContextProps | undefined>(
@@ -38,36 +38,27 @@ const AssistantsContext = createContext<AssistantsContextProps | undefined>(
export const AssistantsProvider: React.FC<{
children: React.ReactNode;
initialAssistants: MinimalPersonaSnapshot[];
hasAnyConnectors?: boolean;
hasImageCompatibleModel?: boolean;
initialAssistants: Persona[];
hasAnyConnectors: boolean;
hasImageCompatibleModel: boolean;
}> = ({
children,
initialAssistants,
hasAnyConnectors,
hasImageCompatibleModel,
}) => {
const [assistants, setAssistants] = useState<MinimalPersonaSnapshot[]>(
const [assistants, setAssistants] = useState<Persona[]>(
initialAssistants || []
);
const { user, isAdmin, isCurator } = useUser();
const [editablePersonas, setEditablePersonas] = useState<
MinimalPersonaSnapshot[]
>([]);
const [allAssistants, setAllAssistants] = useState<MinimalPersonaSnapshot[]>(
[]
);
const [editablePersonas, setEditablePersonas] = useState<Persona[]>([]);
const [allAssistants, setAllAssistants] = useState<Persona[]>([]);
const [pinnedAssistants, setPinnedAssistants] = useState<
MinimalPersonaSnapshot[]
>(() => {
const [pinnedAssistants, setPinnedAssistants] = useState<Persona[]>(() => {
if (user?.preferences.pinned_assistants) {
return user.preferences.pinned_assistants
.map((id) => assistants.find((assistant) => assistant.id === id))
.filter(
(assistant): assistant is MinimalPersonaSnapshot =>
assistant !== undefined
);
.filter((assistant): assistant is Persona => assistant !== undefined);
} else {
return assistants.filter((a) => a.is_default_persona);
}
@@ -78,10 +69,7 @@ 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 MinimalPersonaSnapshot =>
assistant !== undefined
);
.filter((assistant): assistant is Persona => assistant !== undefined);
} else {
return assistants.filter((a) => a.is_default_persona);
}
@@ -147,9 +135,13 @@ export const AssistantsProvider: React.FC<{
},
});
if (!response.ok) throw new Error("Failed to fetch assistants");
let assistants: MinimalPersonaSnapshot[] = await response.json();
let assistants: Persona[] = await response.json();
let filteredAssistants = filterAssistants(assistants);
let filteredAssistants = filterAssistants(
assistants,
hasAnyConnectors,
hasImageCompatibleModel
);
setAssistants(filteredAssistants);

View File

@@ -3,13 +3,17 @@ 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 { TextFormField } from "@/components/Field";
import { BooleanFormField, 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 } from "@/lib/connectors/credentials";
import {
Credential,
credentialTemplates,
getDisplayNameForCredentialKey,
} from "@/lib/connectors/credentials";
import { PlusCircleIcon } from "../../icons/icons";
import { GmailMain } from "@/app/admin/connectors/[connector]/pages/gmail/GmailPage";
import { ActionType, dictionaryType } from "../types";

View File

@@ -1,20 +1,14 @@
import {
MinimalPersonaSnapshot,
Persona,
} from "@/app/admin/assistants/interfaces";
import { Persona } from "@/app/admin/assistants/interfaces";
import { User } from "../types";
import { checkUserIsNoAuthUser } from "../user";
export function checkUserOwnsAssistant(
user: User | null,
assistant: MinimalPersonaSnapshot | Persona
) {
export function checkUserOwnsAssistant(user: User | null, assistant: Persona) {
return checkUserIdOwnsAssistant(user?.id, assistant);
}
export function checkUserIdOwnsAssistant(
userId: string | undefined,
assistant: MinimalPersonaSnapshot | Persona
assistant: Persona
) {
return (
(!userId ||

View File

@@ -1,12 +1,12 @@
import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces";
import { Persona } from "@/app/admin/assistants/interfaces";
import { fetchSS } from "../utilsSS";
export type FetchAssistantsResponse = [MinimalPersonaSnapshot[], string | null];
export type FetchAssistantsResponse = [Persona[], string | null];
export async function fetchAssistantsSS(): Promise<FetchAssistantsResponse> {
const response = await fetchSS("/persona");
if (response.ok) {
return [(await response.json()) as MinimalPersonaSnapshot[], null];
return [(await response.json()) as Persona[], null];
}
return [[], (await response.json()).detail || "Unknown Error"];
}

View File

@@ -1,18 +1,15 @@
import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces";
import { Persona } 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: MinimalPersonaSnapshot
) {
export function checkUserOwnsAssistant(user: User | null, assistant: Persona) {
return checkUserIdOwnsAssistant(user?.id, assistant);
}
export function checkUserIdOwnsAssistant(
userId: string | undefined,
assistant: MinimalPersonaSnapshot
assistant: Persona
) {
return (
(!userId ||
@@ -22,10 +19,7 @@ export function checkUserIdOwnsAssistant(
);
}
export function classifyAssistants(
user: User | null,
assistants: MinimalPersonaSnapshot[]
) {
export function classifyAssistants(user: User | null, assistants: Persona[]) {
if (!user) {
return {
visibleAssistants: assistants.filter(
@@ -65,7 +59,7 @@ export function classifyAssistants(
}
export function orderAssistantsForUser(
assistants: MinimalPersonaSnapshot[],
assistants: Persona[],
user: User | null
) {
let orderedAssistants = [...assistants];
@@ -118,7 +112,7 @@ export function orderAssistantsForUser(
export function getUserCreatedAssistants(
user: User | null,
assistants: MinimalPersonaSnapshot[]
assistants: Persona[]
) {
return assistants.filter((assistant) =>
checkUserOwnsAssistant(user, assistant)
@@ -127,10 +121,29 @@ export function getUserCreatedAssistants(
// Filter assistants based on connector status, image compatibility and visibility
export function filterAssistants(
assistants: MinimalPersonaSnapshot[]
): MinimalPersonaSnapshot[] {
assistants: Persona[],
hasAnyConnectors: boolean,
hasImageCompatibleModel: boolean
): Persona[] {
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);
}

View File

@@ -1,12 +1,12 @@
import { fetchSS } from "@/lib/utilsSS";
import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces";
import { Persona } 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: MinimalPersonaSnapshot[];
assistants: Persona[];
hasAnyConnectors: boolean;
hasImageCompatibleModel: boolean;
}
@@ -51,7 +51,11 @@ export async function fetchAssistantData(): Promise<AssistantData> {
)
);
let filteredAssistants = filterAssistants(assistants);
let filteredAssistants = filterAssistants(
assistants,
hasAnyConnectors,
hasImageCompatibleModel
);
return {
assistants: filteredAssistants,

View File

@@ -18,10 +18,7 @@ import { ChatSession } from "@/app/chat/interfaces";
import { AllUsersResponse } from "./types";
import { Credential } from "./connectors/credentials";
import { SettingsContext } from "@/components/settings/SettingsProvider";
import {
MinimalPersonaSnapshot,
PersonaLabel,
} from "@/app/admin/assistants/interfaces";
import { Persona, 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";
@@ -383,7 +380,7 @@ export interface LlmManager {
updateModelOverrideBasedOnChatSession: (chatSession?: ChatSession) => void;
imageFilesPresent: boolean;
updateImageFilesPresent: (present: boolean) => void;
liveAssistant: MinimalPersonaSnapshot | null;
liveAssistant: Persona | null;
maxTemperature: number;
}
@@ -431,7 +428,7 @@ providing appropriate defaults for new conversations based on the available tool
export function useLlmManager(
llmProviders: LLMProviderDescriptor[],
currentChatSession?: ChatSession,
liveAssistant?: MinimalPersonaSnapshot
liveAssistant?: Persona
): LlmManager {
const { user } = useUser();

View File

@@ -1,4 +1,4 @@
import { MinimalPersonaSnapshot } from "@/app/admin/assistants/interfaces";
import { Persona } from "@/app/admin/assistants/interfaces";
import {
LLMProviderDescriptor,
ModelConfiguration,
@@ -7,7 +7,7 @@ import { LlmDescriptor } from "@/lib/hooks";
export function getFinalLLM(
llmProviders: LLMProviderDescriptor[],
persona: MinimalPersonaSnapshot | null,
persona: Persona | null,
currentLlm: LlmDescriptor | null
): [string, string] {
const defaultProvider = llmProviders.find(
@@ -38,7 +38,7 @@ export function getFinalLLM(
}
export function getLLMProviderOverrideForPersona(
liveAssistant: MinimalPersonaSnapshot,
liveAssistant: Persona,
llmProviders: LLMProviderDescriptor[]
): LlmDescriptor | null {
const overrideProvider = liveAssistant.llm_model_provider_override;