Configurable models + updated assistants bar (#1942)

This commit is contained in:
pablodanswer
2024-07-26 11:00:49 -07:00
committed by GitHub
parent 26a1e963d1
commit c81b45300b
23 changed files with 755 additions and 362 deletions

View File

@@ -0,0 +1,49 @@
"""Add display_model_names to llm_provider
Revision ID: 473a1a7ca408
Revises: 325975216eb3
Create Date: 2024-07-25 14:31:02.002917
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = "473a1a7ca408"
down_revision = "325975216eb3"
branch_labels = None
depends_on = None
default_models_by_provider = {
"openai": ["gpt-4", "gpt-4o", "gpt-4o-mini"],
"bedrock": [
"meta.llama3-1-70b-instruct-v1:0",
"meta.llama3-1-8b-instruct-v1:0",
"anthropic.claude-3-opus-20240229-v1:0",
"mistral.mistral-large-2402-v1:0",
"anthropic.claude-3-5-sonnet-20240620-v1:0",
],
"anthropic": ["claude-3-opus-20240229", "claude-3-5-sonnet-20240620"],
}
def upgrade() -> None:
op.add_column(
"llm_provider",
sa.Column("display_model_names", postgresql.ARRAY(sa.String()), nullable=True),
)
connection = op.get_bind()
for provider, models in default_models_by_provider.items():
connection.execute(
sa.text(
"UPDATE llm_provider SET display_model_names = :models WHERE provider = :provider"
),
{"models": models, "provider": provider},
)
def downgrade() -> None:
op.drop_column("llm_provider", "display_model_names")

View File

@@ -76,6 +76,7 @@ def upsert_llm_provider(
existing_llm_provider.fast_default_model_name = llm_provider.fast_default_model_name
existing_llm_provider.model_names = llm_provider.model_names
existing_llm_provider.is_public = llm_provider.is_public
existing_llm_provider.display_model_names = llm_provider.display_model_names
if not existing_llm_provider.id:
# If its not already in the db, we need to generate an ID by flushing

View File

@@ -928,6 +928,11 @@ class LLMProvider(Base):
default_model_name: Mapped[str] = mapped_column(String)
fast_default_model_name: Mapped[str | None] = mapped_column(String, nullable=True)
# Models to actually disp;aly to users
# If nulled out, we assume in the application logic we should present all
display_model_names: Mapped[list[str] | None] = mapped_column(
postgresql.ARRAY(String), nullable=True
)
# The LLMs that are available for this provider. Only required if not a default provider.
# If a default provider, then the LLM options are pulled from the `options.py` file.
# If needed, can be pulled out as a separate table in the future.

View File

@@ -103,6 +103,7 @@ def port_api_key_to_postgres() -> None:
default_model_name=default_model_name,
fast_default_model_name=default_fast_model_name,
model_names=None,
display_model_names=[],
is_public=True,
)
llm_provider = upsert_llm_provider(db_session, llm_provider_upsert)

View File

@@ -71,6 +71,7 @@ def load_llm_providers(db_session: Session) -> None:
),
model_names=model_names,
is_public=True,
display_model_names=[],
)
llm_provider = upsert_llm_provider(db_session, llm_provider_request)
update_default_provider(db_session, llm_provider.id)

View File

@@ -31,9 +31,7 @@ OPEN_AI_MODEL_NAMES = [
"gpt-4-turbo-preview",
"gpt-4-1106-preview",
"gpt-4-vision-preview",
# "gpt-4-32k", # not EOL but still doesnt work
"gpt-4-0613",
# "gpt-4-32k-0613", # not EOL but still doesnt work
"gpt-4-0314",
"gpt-4-32k-0314",
"gpt-3.5-turbo",
@@ -48,9 +46,11 @@ OPEN_AI_MODEL_NAMES = [
BEDROCK_PROVIDER_NAME = "bedrock"
# need to remove all the weird "bedrock/eu-central-1/anthropic.claude-v1" named
# models
BEDROCK_MODEL_NAMES = [model for model in litellm.bedrock_models if "/" not in model][
::-1
]
BEDROCK_MODEL_NAMES = [
model
for model in litellm.bedrock_models
if "/" not in model and "embed" not in model
][::-1]
IGNORABLE_ANTHROPIC_MODELS = [
"claude-2",

View File

@@ -32,6 +32,7 @@ class LLMProviderDescriptor(BaseModel):
default_model_name: str
fast_default_model_name: str | None
is_default_provider: bool | None
display_model_names: list[str] | None
@classmethod
def from_model(
@@ -48,6 +49,7 @@ class LLMProviderDescriptor(BaseModel):
or fetch_models_for_provider(llm_provider_model.provider)
or [llm_provider_model.default_model_name]
),
display_model_names=llm_provider_model.display_model_names,
)
@@ -62,6 +64,7 @@ class LLMProvider(BaseModel):
fast_default_model_name: str | None
is_public: bool = True
groups: list[int] | None = None
display_model_names: list[str] | None
class LLMProviderUpsertRequest(LLMProvider):
@@ -88,6 +91,7 @@ class FullLLMProvider(LLMProvider):
default_model_name=llm_provider_model.default_model_name,
fast_default_model_name=llm_provider_model.fast_default_model_name,
is_default_provider=llm_provider_model.is_default_provider,
display_model_names=llm_provider_model.display_model_names,
model_names=(
llm_provider_model.model_names
or fetch_models_for_provider(llm_provider_model.provider)

View File

@@ -20,6 +20,7 @@ import {
TextFormField,
} from "@/components/admin/connectors/Field";
import { usePopup } from "@/components/admin/connectors/Popup";
import { getDisplayNameForModel } from "@/lib/hooks";
import { Bubble } from "@/components/Bubble";
import { DocumentSetSelectable } from "@/components/documentSet/DocumentSetSelectable";
import { Option } from "@/components/Dropdown";
@@ -152,7 +153,7 @@ export function AssistantEditor({
llmProviders.forEach((llmProvider) => {
const providerOptions = llmProvider.model_names.map((modelName) => {
return {
name: modelName,
name: getDisplayNameForModel(modelName),
value: modelName,
};
});

View File

@@ -8,12 +8,17 @@ import {
SelectorFormField,
TextFormField,
BooleanFormField,
MultiSelectField,
} from "@/components/admin/connectors/Field";
import { useState } from "react";
import { Bubble } from "@/components/Bubble";
import { GroupsIcon } from "@/components/icons/icons";
import { useSWRConfig } from "swr";
import { useUserGroups } from "@/lib/hooks";
import {
defaultModelsByProvider,
getDisplayNameForModel,
useUserGroups,
} from "@/lib/hooks";
import { FullLLMProvider, WellKnownLLMProviderDescriptor } from "./interfaces";
import { PopupSpec } from "@/components/admin/connectors/Popup";
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
@@ -69,12 +74,12 @@ export function LLMProviderUpdateForm({
),
is_public: existingLlmProvider?.is_public ?? true,
groups: existingLlmProvider?.groups ?? [],
display_model_names:
existingLlmProvider?.display_model_names ||
defaultModelsByProvider[llmProviderDescriptor.name] ||
[],
};
const [validatedConfig, setValidatedConfig] = useState(
existingLlmProvider ? initialValues : null
);
// Setup validation schema if required
const validationSchema = Yup.object({
name: Yup.string().required("Display Name is required"),
@@ -109,6 +114,7 @@ export function LLMProviderUpdateForm({
// EE Only
is_public: Yup.boolean().required(),
groups: Yup.array().of(Yup.number()),
display_model_names: Yup.array().of(Yup.string()),
});
return (
@@ -270,7 +276,7 @@ export function LLMProviderUpdateForm({
subtext="The model to use by default for this provider unless otherwise specified."
label="Default Model"
options={llmProviderDescriptor.llm_names.map((name) => ({
name,
name: getDisplayNameForModel(name),
value: name,
}))}
maxHeight="max-h-56"
@@ -292,7 +298,7 @@ export function LLMProviderUpdateForm({
the Default Model configured above.`}
label="[Optional] Fast Model"
options={llmProviderDescriptor.llm_names.map((name) => ({
name,
name: getDisplayNameForModel(name),
value: name,
}))}
includeDefault
@@ -311,13 +317,33 @@ export function LLMProviderUpdateForm({
<Divider />
<AdvancedOptionsToggle
showAdvancedOptions={showAdvancedOptions}
setShowAdvancedOptions={setShowAdvancedOptions}
/>
{llmProviderDescriptor.name != "azure" && (
<AdvancedOptionsToggle
showAdvancedOptions={showAdvancedOptions}
setShowAdvancedOptions={setShowAdvancedOptions}
/>
)}
{showAdvancedOptions && (
<>
{llmProviderDescriptor.llm_names.length > 0 && (
<div className="w-full">
<MultiSelectField
selectedInitially={values.display_model_names}
name="display_model_names"
label="Display Models"
subtext="Select the models to make available to users. Unselected models will not be available."
options={llmProviderDescriptor.llm_names.map((name) => ({
value: name,
label: getDisplayNameForModel(name),
}))}
onChange={(selected) =>
setFieldValue("display_model_names", selected)
}
/>
</div>
)}
{isPaidEnterpriseFeaturesEnabled && userGroups && (
<>
<BooleanFormField

View File

@@ -32,6 +32,7 @@ export interface LLMProvider {
fast_default_model_name: string | null;
is_public: boolean;
groups: number[];
display_model_names: string[] | null;
}
export interface FullLLMProvider extends LLMProvider {
@@ -50,4 +51,5 @@ export interface LLMProviderDescriptor {
is_default_provider: boolean | null;
is_public: boolean;
groups: number[];
display_model_names: string[] | null;
}

View File

@@ -1,7 +1,14 @@
import { User } from "@/lib/types";
import { Persona } from "../admin/assistants/interfaces";
import { checkUserOwnsAssistant } from "@/lib/assistants/checkOwnership";
import { FiLock, FiUnlock } from "react-icons/fi";
import {
FiImage,
FiLock,
FiMoreHorizontal,
FiSearch,
FiUnlock,
} from "react-icons/fi";
import { CustomTooltip } from "@/components/tooltip/CustomTooltip";
export function AssistantSharedStatusDisplay({
assistant,
@@ -49,6 +56,20 @@ export function AssistantSharedStatusDisplay({
)}
</div>
)}
<div className="relative mt-4 text-xs flex text-subtle">
<span className="font-medium">Powers:</span>{" "}
{assistant.tools.length == 0 ? (
<p className="ml-2">None</p>
) : (
assistant.tools.map((tool, ind) => {
if (tool.name === "SearchTool") {
return <FiSearch key={ind} className="ml-1 h-3 w-3 my-auto" />;
} else if (tool.name === "ImageGenerationTool") {
return <FiImage key={ind} className="ml-1 h-3 w-3 my-auto" />;
}
})
)}
</div>
</div>
);
}

View File

@@ -1,6 +1,9 @@
import { Bubble } from "@/components/Bubble";
import { ToolSnapshot } from "@/lib/tools/interfaces";
import { FiImage, FiSearch, FiGlobe } from "react-icons/fi";
import { FiImage, FiSearch, FiGlobe, FiMoreHorizontal } from "react-icons/fi";
import { Persona } from "../admin/assistants/interfaces";
import { CustomTooltip } from "@/components/tooltip/CustomTooltip";
import { useState } from "react";
export function ToolsDisplay({ tools }: { tools: ToolSnapshot[] }) {
return (
@@ -33,3 +36,97 @@ export function ToolsDisplay({ tools }: { tools: ToolSnapshot[] }) {
</div>
);
}
export function AssistantTools({
assistant,
list,
hovered,
}: {
assistant: Persona;
list?: boolean;
hovered?: boolean;
}) {
return (
<div className="relative text-xs flex text-subtle">
<span
className={`${assistant.tools.length > 0 && "py-1"} ${!list ? "font-semibold" : "text-subtle text-sm"}`}
>
Tools:
</span>{" "}
{assistant.tools.length == 0 ? (
<p className="ml-1">None</p>
) : (
<div className="ml-1 flex flex-wrap gap-1">
{assistant.tools.map((tool, ind) => {
if (tool.name === "SearchTool") {
return (
<div
key={ind}
className={`
px-1.5
py-1
rounded-lg
border
border-border
w-fit
flex
items-center
${hovered ? "bg-background-300" : list ? "bg-background-125" : "bg-background-100"}
cursor-pointer`}
>
<div className="flex gap-x-1">
<FiSearch key={ind} className="ml-1 h-3 w-3 my-auto" />
Search
</div>
</div>
);
} else if (tool.name === "ImageGenerationTool") {
return (
<div
key={ind}
className={`
px-1.5
py-1
rounded-lg
border
border-border
w-fit
flex
${hovered ? "bg-background-300" : list ? "bg-background-125" : "bg-background-100"}
cursor-pointer`}
>
<div className="flex items-center gap-x-1">
<FiImage
key={ind}
className="ml-1 my-auto h-3 w-3 my-auto"
/>
Image Generation
</div>
</div>
);
} else {
return (
<div
key={ind}
className={`
px-1.5
py-1
rounded-lg
border
border-border
w-fit
flex
items-center
${hovered ? "bg-background-300" : list ? "bg-background-125" : "bg-background-100"}
cursor-pointer`}
>
<div className="flex gap-x-1">{tool.name}</div>
</div>
);
}
})}
</div>
)}
</div>
);
}

View File

@@ -15,7 +15,7 @@ import {
} from "@/lib/assistants/updateAssistantPreferences";
import { usePopup } from "@/components/admin/connectors/Popup";
import { useRouter } from "next/navigation";
import { ToolsDisplay } from "../ToolsDisplay";
import { AssistantTools, ToolsDisplay } from "../ToolsDisplay";
export function AssistantsGallery({
assistants,
@@ -99,7 +99,6 @@ export function AssistantsGallery({
className="
text-xl
font-semibold
mb-2
my-auto
ml-2
text-strong
@@ -189,13 +188,14 @@ export function AssistantsGallery({
</div>
)}
</div>
{assistant.tools.length > 0 && (
<ToolsDisplay tools={assistant.tools} />
)}
<p className="text-sm mt-2">{assistant.description}</p>
<p className="text-subtle text-sm mt-2">
<p className="text-subtle text-sm my-2">
Author: {assistant.owner?.email || "Danswer"}
</p>
{assistant.tools.length > 0 && (
<AssistantTools list assistant={assistant} />
)}
</div>
))}
</div>

View File

@@ -13,6 +13,7 @@ import {
FiSearch,
FiX,
FiShare2,
FiImage,
} from "react-icons/fi";
import Link from "next/link";
import { orderAssistantsForUser } from "@/lib/assistants/orderAssistants";
@@ -33,7 +34,8 @@ import { AssistantSharingModal } from "./AssistantSharingModal";
import { AssistantSharedStatusDisplay } from "../AssistantSharedStatus";
import useSWR from "swr";
import { errorHandlingFetcher } from "@/lib/fetcher";
import { ToolsDisplay } from "../ToolsDisplay";
import { AssistantTools, ToolsDisplay } from "../ToolsDisplay";
import { CustomTooltip } from "@/components/tooltip/CustomTooltip";
function AssistantListItem({
assistant,
@@ -73,181 +75,188 @@ function AssistantListItem({
show={showSharingModal}
/>
<div
className="
bg-background-emphasis
className="flex bg-background-emphasis
rounded-lg
shadow-md
p-4
mb-4
mb-4 flex-col"
>
<div
className="
flex
justify-between
items-center
"
>
<div className="w-3/4">
<div className="flex items-center">
<AssistantIcon assistant={assistant} />
<h2 className="text-xl font-semibold my-auto ml-2">
{assistant.name}
</h2>
</div>
{assistant.tools.length > 0 && (
<ToolsDisplay tools={assistant.tools} />
)}
<div className="text-sm mt-2">{assistant.description}</div>
<div className="mt-2">
<AssistantSharedStatusDisplay assistant={assistant} user={user} />
</div>
</div>
{isOwnedByUser && (
<div className="ml-auto flex items-center">
{!assistant.is_public && (
<div
className="mr-4 rounded p-2 cursor-pointer hover:bg-hover"
onClick={() => setShowSharingModal(true)}
>
<FiShare2 size={16} />
</div>
)}
<Link
href={`/assistants/edit/${assistant.id}`}
className="mr-4 rounded p-2 cursor-pointer hover:bg-hover"
>
<FiEdit2 size={16} />
</Link>
</div>
)}
<DefaultPopover
content={
<div className="hover:bg-hover rounded p-2 cursor-pointer">
<FiMoreHorizontal size={16} />
</div>
}
side="bottom"
align="start"
sideOffset={5}
>
{[
...(!isFirst
? [
<div
key="move-up"
className="flex items-center gap-x-2"
onClick={async () => {
const success = await moveAssistantUp(
assistant.id,
currentChosenAssistants || allAssistantIds
);
if (success) {
setPopup({
message: `"${assistant.name}" has been moved up.`,
type: "success",
});
router.refresh();
} else {
setPopup({
message: `"${assistant.name}" could not be moved up.`,
type: "error",
});
}
}}
>
<FiArrowUp /> Move Up
</div>,
]
: []),
...(!isLast
? [
<div
key="move-down"
className="flex items-center gap-x-2"
onClick={async () => {
const success = await moveAssistantDown(
assistant.id,
currentChosenAssistants || allAssistantIds
);
if (success) {
setPopup({
message: `"${assistant.name}" has been moved down.`,
type: "success",
});
router.refresh();
} else {
setPopup({
message: `"${assistant.name}" could not be moved down.`,
type: "error",
});
}
}}
>
<FiArrowDown /> Move Down
</div>,
]
: []),
isVisible ? (
<div
key="remove"
className="flex items-center gap-x-2"
onClick={async () => {
if (
currentChosenAssistants &&
currentChosenAssistants.length === 1
) {
setPopup({
message: `Cannot remove "${assistant.name}" - you must have at least one assistant.`,
type: "error",
});
return;
}
<div className="w-3/4">
<div className="flex items-center">
<AssistantIcon assistant={assistant} />
<h2 className="text-xl font-semibold my-auto ml-2">
{assistant.name}
</h2>
</div>
const success = await removeAssistantFromList(
assistant.id,
currentChosenAssistants || allAssistantIds
);
if (success) {
setPopup({
message: `"${assistant.name}" has been removed from your list.`,
type: "success",
});
router.refresh();
} else {
setPopup({
message: `"${assistant.name}" could not be removed from your list.`,
type: "error",
});
}
}}
<div className="text-sm mt-2">{assistant.description}</div>
<div className="mt-2 flex items-center gap-x-3">
<AssistantSharedStatusDisplay assistant={assistant} user={user} />
{assistant.tools.length != 0 && (
<AssistantTools list assistant={assistant} />
)}
</div>
</div>
{isOwnedByUser && (
<div className="ml-auto flex items-center">
{!assistant.is_public && (
<div
className="mr-4 rounded p-2 cursor-pointer hover:bg-hover"
onClick={() => setShowSharingModal(true)}
>
<FiShare2 size={16} />
</div>
)}
<Link
href={`/assistants/edit/${assistant.id}`}
className="mr-4 rounded p-2 cursor-pointer hover:bg-hover"
>
<FiX /> {isOwnedByUser ? "Hide" : "Remove"}
<FiEdit2 size={16} />
</Link>
</div>
)}
<DefaultPopover
content={
<div className="hover:bg-hover rounded p-2 cursor-pointer">
<FiMoreHorizontal size={16} />
</div>
) : (
<div
key="add"
className="flex items-center gap-x-2"
onClick={async () => {
const success = await addAssistantToList(
assistant.id,
currentChosenAssistants || allAssistantIds
);
if (success) {
setPopup({
message: `"${assistant.name}" has been added to your list.`,
type: "success",
});
router.refresh();
} else {
setPopup({
message: `"${assistant.name}" could not be added to your list.`,
type: "error",
});
}
}}
>
<FiPlus /> Add
</div>
),
]}
</DefaultPopover>
}
side="bottom"
align="start"
sideOffset={5}
>
{[
...(!isFirst
? [
<div
key="move-up"
className="flex items-center gap-x-2"
onClick={async () => {
const success = await moveAssistantUp(
assistant.id,
currentChosenAssistants || allAssistantIds
);
if (success) {
setPopup({
message: `"${assistant.name}" has been moved up.`,
type: "success",
});
router.refresh();
} else {
setPopup({
message: `"${assistant.name}" could not be moved up.`,
type: "error",
});
}
}}
>
<FiArrowUp /> Move Up
</div>,
]
: []),
...(!isLast
? [
<div
key="move-down"
className="flex items-center gap-x-2"
onClick={async () => {
const success = await moveAssistantDown(
assistant.id,
currentChosenAssistants || allAssistantIds
);
if (success) {
setPopup({
message: `"${assistant.name}" has been moved down.`,
type: "success",
});
router.refresh();
} else {
setPopup({
message: `"${assistant.name}" could not be moved down.`,
type: "error",
});
}
}}
>
<FiArrowDown /> Move Down
</div>,
]
: []),
isVisible ? (
<div
key="remove"
className="flex items-center gap-x-2"
onClick={async () => {
if (
currentChosenAssistants &&
currentChosenAssistants.length === 1
) {
setPopup({
message: `Cannot remove "${assistant.name}" - you must have at least one assistant.`,
type: "error",
});
return;
}
const success = await removeAssistantFromList(
assistant.id,
currentChosenAssistants || allAssistantIds
);
if (success) {
setPopup({
message: `"${assistant.name}" has been removed from your list.`,
type: "success",
});
router.refresh();
} else {
setPopup({
message: `"${assistant.name}" could not be removed from your list.`,
type: "error",
});
}
}}
>
<FiX /> {isOwnedByUser ? "Hide" : "Remove"}
</div>
) : (
<div
key="add"
className="flex items-center gap-x-2"
onClick={async () => {
const success = await addAssistantToList(
assistant.id,
currentChosenAssistants || allAssistantIds
);
if (success) {
setPopup({
message: `"${assistant.name}" has been added to your list.`,
type: "success",
});
router.refresh();
} else {
setPopup({
message: `"${assistant.name}" could not be added to your list.`,
type: "error",
});
}
}}
>
<FiPlus /> Add
</div>
),
]}
</DefaultPopover>
</div>
</div>
</>
);

View File

@@ -2,7 +2,11 @@ import React, { useEffect, useRef, useState } from "react";
import { FiPlusCircle, FiPlus, FiInfo, FiX } from "react-icons/fi";
import { ChatInputOption } from "./ChatInputOption";
import { Persona } from "@/app/admin/assistants/interfaces";
import { FilterManager, LlmOverrideManager } from "@/lib/hooks";
import {
FilterManager,
getDisplayNameForModel,
LlmOverrideManager,
} from "@/lib/hooks";
import { SelectedFilterDisplay } from "./SelectedFilterDisplay";
import { useChatContext } from "@/components/context/ChatContext";
import { getFinalLLM } from "@/lib/llm/utils";
@@ -428,8 +432,16 @@ export function ChatInputBar({
</Popup>
<Popup
tab
content={(close, ref) => (
<LlmTab
currentLlm={
llmOverrideManager.llmOverride.modelName ||
(selectedAssistant
? selectedAssistant.llm_model_version_override ||
llmName
: llmName)
}
close={close}
ref={ref}
llmOverrideManager={llmOverrideManager}
@@ -441,12 +453,13 @@ export function ChatInputBar({
>
<ChatInputOption
flexPriority="second"
name={
name={getDisplayNameForModel(
llmOverrideManager.llmOverride.modelName ||
(selectedAssistant
? selectedAssistant.llm_model_version_override || llmName
: llmName)
}
(selectedAssistant
? selectedAssistant.llm_model_version_override ||
llmName
: llmName)
)}
Icon={CpuIconSkeleton}
/>
</Popup>

View File

@@ -1,10 +1,12 @@
import { Persona } from "@/app/admin/assistants/interfaces";
import { LLMProviderDescriptor } from "@/app/admin/models/llm/interfaces";
import { AssistantTools } from "@/app/assistants/ToolsDisplay";
import { Bubble } from "@/components/Bubble";
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
import { getDisplayNameForModel } from "@/lib/hooks";
import { getFinalLLM } from "@/lib/llm/utils";
import React from "react";
import { FiBookmark, FiImage, FiSearch } from "react-icons/fi";
import React, { useState } from "react";
import { FiBookmark, FiPlus } from "react-icons/fi";
interface AssistantsTabProps {
selectedAssistant: Persona;
@@ -24,80 +26,82 @@ export function AssistantsTab({
return (
<div className="py-4">
<h3 className="px-4 text-lg font-semibold">Change Assistant</h3>
<div className="px-2 mx-2 max-h-[500px] overflow-y-scroll my-3 grid grid-cols-2 gap-4">
<div className="px-2 pb-2 mx-2 max-h-[500px] overflow-y-scroll my-3 grid grid-cols-1 gap-4">
{availableAssistants.map((assistant) => (
<div
<AssistantCard
key={assistant.id}
className={`
cursor-pointer
p-4
border
rounded-lg
shadow-md
hover:bg-hover-light
${
selectedAssistant.id === assistant.id
? "border-accent"
: "border-border"
}
`}
onClick={() => onSelect(assistant)}
>
<div className="flex items-center mb-2">
<AssistantIcon assistant={assistant} />
<div className="ml-2 line-clamp-2 ellipsis font-bold text-sm text-emphasis">
{assistant.name}
</div>
</div>
{assistant.tools.length > 0 && (
<div className="text-xs text-subtle flex flex-wrap gap-2">
{assistant.tools.map((tool) => {
let toolName = tool.name;
let toolIcon = null;
if (tool.name === "SearchTool") {
toolName = "Search";
toolIcon = <FiSearch className="mr-1 my-auto" />;
} else if (tool.name === "ImageGenerationTool") {
toolName = "Image Generation";
toolIcon = <FiImage className="mr-1 my-auto" />;
}
return (
<Bubble key={tool.id} isSelected={false}>
<div className="flex line-wrap break-all flex-row gap-1">
<div className="flex-none my-auto">{toolIcon}</div>
{toolName}
</div>
</Bubble>
);
})}
</div>
)}
<div className="text-xs text-subtle mb-2 mt-2">
{assistant.description}
</div>
<div className="mt-2 flex flex-col gap-y-2">
{assistant.document_sets.length > 0 && (
<div className="text-xs text-subtle flex flex-wrap gap-2">
<p className="my-auto font-medium">Document Sets:</p>
{assistant.document_sets.map((set) => (
<Bubble key={set.id} isSelected={false}>
<div className="flex flex-row gap-1">
<FiBookmark className="mr-1 my-auto" />
{set.name}
</div>
</Bubble>
))}
</div>
)}
<div className="text-xs text-subtle">
<span className="font-medium">Default Model:</span>{" "}
<i>{assistant.llm_model_version_override || llmName}</i>
</div>
</div>
</div>
assistant={assistant}
isSelected={selectedAssistant.id === assistant.id}
onSelect={onSelect}
llmName={llmName}
/>
))}
</div>
</div>
);
}
const AssistantCard = ({
assistant,
isSelected,
onSelect,
llmName,
}: {
assistant: Persona;
isSelected: boolean;
onSelect: (assistant: Persona) => void;
llmName: string;
}) => {
const [hovering, setHovering] = useState(false);
return (
<div
onClick={() => onSelect(assistant)}
key={assistant.id}
className={`
p-4
cursor-pointer
border
hover:bg-hover
shadow-md
rounded
rounded-lg
border-border
`}
onMouseEnter={() => setHovering(true)}
onMouseLeave={() => setHovering(false)}
>
<div className="flex items-center mb-2">
<AssistantIcon assistant={assistant} />
<div className="ml-2 line-clamp-1 ellipsis font-bold text-sm text-emphasis">
{assistant.name}
</div>
</div>
<div className="text-xs text-subtle mb-2 mt-2 line-clamp-3 py-1">
{assistant.description}
</div>
<div className="mt-2 flex flex-col gap-y-1">
{assistant.document_sets.length > 0 && (
<div className="text-xs text-subtle flex flex-wrap gap-2">
<p className="my-auto font-medium">Document Sets:</p>
{assistant.document_sets.map((set) => (
<Bubble key={set.id} isSelected={false}>
<div className="flex flex-row gap-1">
<FiBookmark className="mr-1 my-auto" />
{set.name}
</div>
</Bubble>
))}
</div>
)}
<div className="text-xs text-subtle">
<span className="font-semibold">Default model:</span>{" "}
{getDisplayNameForModel(
assistant.llm_model_version_override || llmName
)}
</div>
<AssistantTools hovered={hovering} assistant={assistant} />
</div>
</div>
);
};

View File

@@ -1,5 +1,5 @@
import { useChatContext } from "@/components/context/ChatContext";
import { LlmOverride, LlmOverrideManager } from "@/lib/hooks";
import { getDisplayNameForModel, LlmOverrideManager } from "@/lib/hooks";
import React, { forwardRef, useCallback, useRef, useState } from "react";
import { debounce } from "lodash";
import { DefaultDropdown } from "@/components/Dropdown";
@@ -7,20 +7,26 @@ import { Text } from "@tremor/react";
import { Persona } from "@/app/admin/assistants/interfaces";
import { destructureValue, getFinalLLM, structureValue } from "@/lib/llm/utils";
import { updateModelOverrideForChatSession } from "../../lib";
import { Tooltip } from "@/components/tooltip/Tooltip";
import { InfoIcon } from "@/components/icons/icons";
import { CustomTooltip } from "@/components/tooltip/CustomTooltip";
interface LlmTabProps {
llmOverrideManager: LlmOverrideManager;
currentAssistant: Persona;
currentLlm: string;
chatSessionId?: number;
close?: () => void;
close: () => void;
}
export const LlmTab = forwardRef<HTMLDivElement, LlmTabProps>(
({ llmOverrideManager, currentAssistant, chatSessionId, close }, ref) => {
(
{ llmOverrideManager, currentAssistant, chatSessionId, currentLlm, close },
ref
) => {
const { llmProviders } = useChatContext();
const { llmOverride, setLlmOverride, temperature, setTemperature } =
llmOverrideManager;
const { setLlmOverride, temperature, setTemperature } = llmOverrideManager;
const [isTemperatureExpanded, setIsTemperatureExpanded] = useState(false);
const [localTemperature, setLocalTemperature] = useState<number>(
temperature || 0
);
@@ -44,94 +50,97 @@ export const LlmTab = forwardRef<HTMLDivElement, LlmTabProps>(
);
const llmOptions: { name: string; value: string }[] = [];
llmProviders.forEach((llmProvider) => {
llmProvider.model_names.forEach((modelName) => {
llmOptions.push({
name: modelName,
value: structureValue(
llmProvider.name,
llmProvider.provider,
modelName
),
});
});
(llmProvider.display_model_names || llmProvider.model_names).forEach(
(modelName) => {
llmOptions.push({
name: modelName,
value: structureValue(
llmProvider.name,
llmProvider.provider,
modelName
),
});
}
);
});
return (
<div className="mb-4">
<label className="block text-sm font-medium mb-2">Choose Model</label>
<Text className="mb-1">
Override the default model for the{" "}
<i className="font-medium">{currentAssistant.name}</i> assistant. The
override will only apply for the current chat session.
</Text>
<Text className="mb-3">
Default Model: <i className="font-medium">{defaultLlmName}</i>.
</Text>
<div ref={ref} className="w-96">
<DefaultDropdown
ref={ref}
options={llmOptions}
selected={structureValue(
llmOverride.name,
llmOverride.provider,
llmOverride.modelName
)}
onSelect={(value) => {
setLlmOverride(destructureValue(value as string));
if (chatSessionId) {
updateModelOverrideForChatSession(
chatSessionId,
value as string
);
}
}}
/>
<div className="w-full">
<div className="flex w-full content-center gap-x-2">
<label className="block text-sm font-medium mb-2">Choose Model</label>
</div>
<label className="block text-sm font-medium mb-2 mt-4">
Temperature
</label>
<Text className="mb-8">
Adjust the temperature of the LLM. Higher temperatures will make the
LLM generate more creative and diverse responses, while lower
temperature will make the LLM generate more conservative and focused
responses.
</Text>
<div className="relative w-full">
<input
type="range"
onChange={(e) =>
handleTemperatureChange(parseFloat(e.target.value))
}
className="
w-full
p-2
border
border-border
rounded-md
"
min="0"
max="2"
step="0.01"
value={localTemperature}
/>
<div
className="absolute text-sm"
style={{
left: `${(localTemperature || 0) * 50}%`,
transform: `translateX(-${Math.min(
Math.max((localTemperature || 0) * 50, 10),
90
)}%)`,
top: "-1.5rem",
}}
<div className="max-h-[300px] flex flex-col gap-y-1 overflow-y-scroll">
{llmOptions.map(({ name, value }, index) => {
return (
<button
key={index}
className={`w-full py-1.5 px-2 text-sm ${currentLlm == name ? "bg-background-200" : "bg-background-100/50 hover:bg-background-100"} text-left rounded`}
onClick={() => {
setLlmOverride(destructureValue(value));
if (chatSessionId) {
updateModelOverrideForChatSession(
chatSessionId,
value as string
);
}
close();
}}
>
{getDisplayNameForModel(name)}
</button>
);
})}
</div>
<div className="mt-4">
<button
className="flex items-center text-sm font-medium transition-colors duration-200"
onClick={() => setIsTemperatureExpanded(!isTemperatureExpanded)}
>
{localTemperature}
</div>
<span className="mr-2 text-xs text-primary">
{isTemperatureExpanded ? "▼" : "►"}
</span>
<span>Temperature</span>
</button>
{isTemperatureExpanded && (
<>
<Text className="mt-2 mb-8">
Adjust the temperature of the LLM. Higher temperatures will make
the LLM generate more creative and diverse responses, while
lower temperature will make the LLM generate more conservative
and focused responses.
</Text>
<div className="relative w-full">
<input
type="range"
onChange={(e) =>
handleTemperatureChange(parseFloat(e.target.value))
}
className="w-full p-2 border border-border rounded-md"
min="0"
max="2"
step="0.01"
value={localTemperature}
/>
<div
className="absolute text-sm"
style={{
left: `${(localTemperature || 0) * 50}%`,
transform: `translateX(-${Math.min(
Math.max((localTemperature || 0) * 50, 10),
90
)}%)`,
top: "-1.5rem",
}}
>
{localTemperature}
</div>
</div>
</>
)}
</div>
</div>
);

View File

@@ -17,7 +17,7 @@ export function Bubble({
<div
className={
`
px-3
px-1.5
py-1
rounded-lg
border

View File

@@ -191,6 +191,77 @@ export function TextFormField({
);
}
export function MultiSelectField({
name,
label,
subtext,
options,
onChange,
error,
hideError,
small,
selectedInitially,
}: {
selectedInitially: string[];
name: string;
label: string;
subtext?: string | JSX.Element;
options: { value: string; label: string }[];
onChange?: (selected: string[]) => void;
error?: string;
hideError?: boolean;
small?: boolean;
}) {
const [selectedOptions, setSelectedOptions] =
useState<string[]>(selectedInitially);
const handleCheckboxChange = (value: string) => {
const newSelectedOptions = selectedOptions.includes(value)
? selectedOptions.filter((option) => option !== value)
: [...selectedOptions, value];
setSelectedOptions(newSelectedOptions);
if (onChange) {
onChange(newSelectedOptions);
}
};
return (
<div className="mb-6">
<div className="flex gap-x-2 items-center">
<Label small={small}>{label}</Label>
{error ? (
<ManualErrorMessage>{error}</ManualErrorMessage>
) : (
!hideError && (
<ErrorMessage
name={name}
component="div"
className="text-error my-auto text-sm"
/>
)
)}
</div>
{subtext && <SubLabel>{subtext}</SubLabel>}
<div className="mt-2">
{options.map((option) => (
<label key={option.value} className="flex items-center mb-2">
<input
type="checkbox"
name={name}
value={option.value}
checked={selectedOptions.includes(option.value)}
onChange={() => handleCheckboxChange(option.value)}
className="mr-2"
/>
{option.label}
</label>
))}
</div>
</div>
);
}
interface MarkdownPreviewProps {
name: string;
label: string;

View File

@@ -8,11 +8,13 @@ interface PopupProps {
) => ReactNode;
position?: "top" | "bottom" | "left" | "right";
removePadding?: boolean;
tab?: boolean;
}
const Popup: React.FC<PopupProps> = ({
children,
content,
tab,
removePadding,
position = "top",
}) => {
@@ -76,7 +78,7 @@ const Popup: React.FC<PopupProps> = ({
break;
default: // top
popupStyle.bottom = `${triggerRect.height + 5}px`;
popupStyle.left = `${triggerRect.width + 100}px`;
popupStyle.left = `${triggerRect.width + 50}px`;
popupStyle.transform = "translateX(-50%)";
}
@@ -94,7 +96,7 @@ const Popup: React.FC<PopupProps> = ({
ref={popupRef}
className={`absolute bg-white border border-gray-200 rounded-lg shadow-lg ${
!removePadding && "p-4"
} min-w-[400px]`}
} ${tab ? " w-[400px] " : "min-w-[400px]"}`}
style={getPopupStyle()}
>
{content(closePopup, contentRef)}

View File

@@ -42,6 +42,7 @@ export const CustomTooltip = ({
line,
showTick = false,
delay = 500,
position = "bottom",
}: {
content: string | ReactNode;
children: JSX.Element;
@@ -51,6 +52,7 @@ export const CustomTooltip = ({
showTick?: boolean;
delay?: number;
citation?: boolean;
position?: "top" | "bottom";
}) => {
const [isVisible, setIsVisible] = useState(false);
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
@@ -99,8 +101,8 @@ export const CustomTooltip = ({
</span>
{isVisible && (
<div
className={`absolute z-10 ${citation ? "max-w-[350px]" : "w-40"} ${large ? "w-96" : line && "max-w-64 w-auto"}
left-1/2 transform -translate-x-1/2 mt-2 text-sm
className={`absolute z-[1000] ${citation ? "max-w-[350px]" : "w-40"} ${large ? "w-96" : line && "max-w-64 w-auto"}
left-1/2 transform -translate-x-1/2 ${position === "top" ? "bottom-full mb-2" : "mt-2"} text-sm
${
light
? "text-gray-800 bg-background-200"
@@ -110,7 +112,7 @@ export const CustomTooltip = ({
>
{showTick && (
<div
className={`absolute w-3 h-3 -top-1.5 left-1/2 transform -translate-x-1/2 rotate-45
className={`absolute w-3 h-3 -top-1.5 ${position === "top" ? "bottom-1.5" : "-top-1.5"} left-1/2 transform -translate-x-1/2 rotate-45
${light ? "bg-background-200" : "bg-background-800"}`}
/>
)}

View File

@@ -90,6 +90,7 @@ export async function fetchChatData(searchParams: {
const tagsResponse = results[6] as Response | null;
const llmProviders = (results[7] || []) as LLMProviderDescriptor[];
const foldersResponse = results[8] as Response | null; // Handle folders result
const authDisabled = authTypeMetadata?.authType === "disabled";

View File

@@ -210,3 +210,77 @@ export const useUserGroups = (): {
refreshUserGroups: () => mutate(USER_GROUP_URL),
};
};
const MODEL_DISPLAY_NAMES: { [key: string]: string } = {
// OpenAI models
"gpt-4": "GPT 4",
"gpt-4o": "GPT 4o",
"gpt-4o-mini": "GPT 4o Mini",
"gpt-4-0314": "GPT 4 (March 2023)",
"gpt-4-0613": "GPT 4 (June 2023)",
"gpt-4-32k-0314": "GPT 4 32k (March 2023)",
"gpt-4-turbo": "GPT 4 Turbo",
"gpt-4-turbo-preview": "GPT 4 Turbo (Preview)",
"gpt-4-1106-preview": "GPT 4 Turbo (November 2023)",
"gpt-4-vision-preview": "GPT 4 Vision (Preview)",
"gpt-3.5-turbo": "GPT 3.5 Turbo",
"gpt-3.5-turbo-0125": "GPT 3.5 Turbo (January 2024)",
"gpt-3.5-turbo-1106": "GPT 3.5 Turbo (November 2023)",
"gpt-3.5-turbo-16k": "GPT 3.5 Turbo 16k",
"gpt-3.5-turbo-0613": "GPT 3.5 Turbo (June 2023)",
"gpt-3.5-turbo-16k-0613": "GPT 3.5 Turbo 16k (June 2023)",
"gpt-3.5-turbo-0301": "GPT 3.5 Turbo (March 2023)",
// Anthropic models
"claude-3-opus-20240229": "Claude 3 Opus",
"claude-3-sonnet-20240229": "Claude 3 Sonnet",
"claude-3-haiku-20240307": "Claude 3 Haiku",
"claude-2.1": "Claude 2.1",
"claude-2.0": "Claude 2.0",
"claude-instant-1.2": "Claude Instant 1.2",
"claude-3-5-sonnet-20240620": "Claude 3.5 Sonnet",
// Bedrock models
"meta.llama3-1-70b-instruct-v1:0": "Llama 3.1 70B",
"meta.llama3-1-8b-instruct-v1:0": "Llama 3.1 8B",
"meta.llama3-70b-instruct-v1:0": "Llama 3 70B",
"meta.llama3-8b-instruct-v1:0": "Llama 3 8B",
"meta.llama2-70b-chat-v1": "Llama 2 70B",
"meta.llama2-13b-chat-v1": "Llama 2 13B",
"cohere.command-r-v1:0": "Command R",
"cohere.command-r-plus-v1:0": "Command R Plus",
"cohere.command-light-text-v14": "Command Light Text",
"cohere.command-text-v14": "Command Text",
"anthropic.claude-instant-v1": "Claude Instant",
"anthropic.claude-v2:1": "Claude v2.1",
"anthropic.claude-v2": "Claude v2",
"anthropic.claude-v1": "Claude v1",
"anthropic.claude-3-opus-20240229-v1:0": "Claude 3 Opus",
"anthropic.claude-3-haiku-20240307-v1:0": "Claude 3 Haiku",
"anthropic.claude-3-5-sonnet-20240620-v1:0": "Claude 3.5 Sonnet",
"anthropic.claude-3-sonnet-20240229-v1:0": "Claude 3 Sonnet",
"mistral.mistral-large-2402-v1:0": "Mistral Large",
"mistral.mixtral-8x7b-instruct-v0:1": "Mixtral 8x7B Instruct",
"mistral.mistral-7b-instruct-v0:2": "Mistral 7B Instruct",
"amazon.titan-text-express-v1": "Titan Text Express",
"amazon.titan-text-lite-v1": "Titan Text Lite",
"ai21.jamba-instruct-v1:0": "Jamba Instruct",
"ai21.j2-ultra-v1": "J2 Ultra",
"ai21.j2-mid-v1": "J2 Mid",
};
export function getDisplayNameForModel(modelName: string): string {
return MODEL_DISPLAY_NAMES[modelName] || modelName;
}
export const defaultModelsByProvider: { [name: string]: string[] } = {
openai: ["gpt-4", "gpt-4o", "gpt-4o-mini"],
bedrock: [
"meta.llama3-1-70b-instruct-v1:0",
"meta.llama3-1-8b-instruct-v1:0",
"anthropic.claude-3-opus-20240229-v1:0",
"mistral.mistral-large-2402-v1:0",
"anthropic.claude-3-5-sonnet-20240620-v1:0",
],
anthropic: ["claude-3-opus-20240229", "claude-3-5-sonnet-20240620"],
};