update persona defaults (#3042)

* evaluate None to default

* fix usage report pagination

* update persona defaults

* update user preferences

* k

* validate

* update typing

* nit

* formating nits

* fallback to all assistants

* update ux + spacing

* udpate refresh logic

* minor update to refresh

* nit

* touchup

* update starter message

* update default live assistant logic

---------

Co-authored-by: Yuhong Sun <yuhongsun96@gmail.com>
This commit is contained in:
pablodanswer
2024-11-06 16:03:14 -08:00
committed by GitHub
parent 43d8daa5bc
commit 07a1b49b4f
25 changed files with 532 additions and 265 deletions

View File

@@ -246,9 +246,6 @@ export function AssistantEditor({
name: Yup.string().required(
"Each starter message must have a name"
),
description: Yup.string().required(
"Each starter message must have a description"
),
message: Yup.string().required(
"Each starter message must have a message"
),
@@ -1053,36 +1050,6 @@ export function AssistantEditor({
/>
</div>
<div className="mt-3">
<Label small>Description</Label>
<SubLabel>
A description which tells the user
what they might want to use this
Starter Message for. For example
&quot;to a client about a new
feature&quot;
</SubLabel>
<Field
name={`starter_messages.${index}.description`}
className={`
border
border-border
bg-background
rounded
w-full
py-2
px-3
mr-4
`}
autoComplete="off"
/>
<ErrorMessage
name={`starter_messages[${index}].description`}
component="div"
className="text-error text-sm mt-1"
/>
</div>
<div className="mt-3">
<Label small>Message</Label>
<SubLabel>

View File

@@ -3,7 +3,6 @@ import { DocumentSet, MinimalUserSnapshot } from "@/lib/types";
export interface StarterMessage {
name: string;
description: string | null;
message: string;
}

View File

@@ -191,19 +191,15 @@ const DocumentSetTable = ({
</TableCell>
<TableCell>
{documentSet.is_up_to_date ? (
<Badge size="md" variant="success" icon={FiCheckCircle}>
<Badge variant="success" icon={FiCheckCircle}>
Up to Date
</Badge>
) : documentSet.cc_pair_descriptors.length > 0 ? (
<Badge size="md" variant="in_progress" icon={FiClock}>
<Badge variant="in_progress" icon={FiClock}>
Syncing
</Badge>
) : (
<Badge
size="md"
variant="destructive"
icon={FiAlertTriangle}
>
<Badge variant="destructive" icon={FiAlertTriangle}>
Deleting
</Badge>
)}
@@ -211,7 +207,6 @@ const DocumentSetTable = ({
<TableCell>
{documentSet.is_public ? (
<Badge
size="md"
variant={isEditable ? "success" : "default"}
icon={FiUnlock}
>
@@ -219,7 +214,6 @@ const DocumentSetTable = ({
</Badge>
) : (
<Badge
size="md"
variant={isEditable ? "in_progress" : "outline"}
icon={FiLock}
>

View File

@@ -200,8 +200,8 @@ function ConnectorRow({
router.push(`/admin/connector/${ccPairsIndexingStatus.cc_pair_id}`);
}}
>
<TableCell className="!w-[300px]">
<p className="w-[200px] xl:w-[400px] inline-block ellipsis truncate">
<TableCell className="">
<p className="lg:w-[200px] xl:w-[400px] inline-block ellipsis truncate">
{ccPairsIndexingStatus.name}
</p>
</TableCell>

View File

@@ -1,46 +1,42 @@
import { Persona } from "../admin/assistants/interfaces";
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
import { useState } from "react";
import { DisplayAssistantCard } from "@/components/assistants/AssistantCards";
import { DisplayAssistantCard } from "@/components/assistants/AssistantDescriptionCard";
export function ChatIntro({ selectedPersona }: { selectedPersona: Persona }) {
const [hoveredAssistant, setHoveredAssistant] = useState(false);
return (
<>
<div className="mobile:w-[90%] mobile:px-4 w-message-xs 2xl:w-message-sm 3xl:w-message">
<div className="relative flex w-fit mx-auto justify-center">
<div className="absolute z-10 -left-20 top-1/2 -translate-y-1/2">
<div className="relative">
<div
onMouseEnter={() => setHoveredAssistant(true)}
onMouseLeave={() => setHoveredAssistant(false)}
className="p-4 scale-[.8] cursor-pointer border-dashed rounded-full flex border border-border border-2 border-dashed"
style={{
borderStyle: "dashed",
borderWidth: "1.5px",
borderSpacing: "4px",
}}
>
<AssistantIcon
disableToolip
size={"large"}
assistant={selectedPersona}
/>
</div>
<div className="absolute right-full mr-2 w-[300px] top-0">
{hoveredAssistant && (
<DisplayAssistantCard selectedPersona={selectedPersona} />
)}
</div>
<div className="flex flex-col items-center gap-6">
<div className="relative flex w-fit mx-auto justify-center">
<div className="absolute z-10 -left-20 top-1/2 -translate-y-1/2">
<div className="relative">
<div
onMouseEnter={() => setHoveredAssistant(true)}
onMouseLeave={() => setHoveredAssistant(false)}
className="p-4 scale-[.7] cursor-pointer border-dashed rounded-full flex border border-gray-300 border-2 border-dashed"
>
<AssistantIcon
disableToolip
size="large"
assistant={selectedPersona}
/>
</div>
<div className="absolute right-full mr-1 w-[300px] top-0">
{hoveredAssistant && (
<DisplayAssistantCard selectedPersona={selectedPersona} />
)}
</div>
</div>
</div>
<div className="text-3xl line-clamp-2 text-text-800 font-base font-semibold text-strong">
{selectedPersona?.name || "How can I help you today?"}
</div>
<div className="text-2xl text-black font-semibold text-center">
{selectedPersona.name}
</div>
</div>
</>
<p className="text-base text-black font-normal text-center">
{selectedPersona.description}
</p>
</div>
);
}

View File

@@ -66,7 +66,7 @@ import { ShareChatSessionModal } from "./modal/ShareChatSessionModal";
import { FiArrowDown } from "react-icons/fi";
import { ChatIntro } from "./ChatIntro";
import { AIMessage, HumanMessage } from "./message/Messages";
import { StarterMessage } from "./StarterMessage";
import { StarterMessages } from "../../components/assistants/StarterMessage";
import {
AnswerPiecePacket,
DanswerDocument,
@@ -104,6 +104,14 @@ import BlurBackground from "./shared_chat_search/BlurBackground";
import { NoAssistantModal } from "@/components/modals/NoAssistantModal";
import { useAssistants } from "@/components/context/AssistantsContext";
import { Separator } from "@/components/ui/separator";
import {
Card,
CardContent,
CardDescription,
CardHeader,
} from "@/components/ui/card";
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
import AssistantBanner from "../../components/assistants/AssistantBanner";
const TEMP_USER_MESSAGE_ID = -1;
const TEMP_ASSISTANT_MESSAGE_ID = -2;
@@ -140,7 +148,7 @@ export function ChatPage({
!shouldShowWelcomeModal
);
const { user, isAdmin, isLoadingUser } = useUser();
const { user, isAdmin, isLoadingUser, refreshUser } = useUser();
const existingChatIdRaw = searchParams.get("chatId");
const [sendOnLoad, setSendOnLoad] = useState<string | null>(
@@ -233,9 +241,17 @@ export function ChatPage({
const [alternativeAssistant, setAlternativeAssistant] =
useState<Persona | null>(null);
const {
visibleAssistants: assistants,
recentAssistants,
assistants: allAssistants,
refreshRecentAssistants,
} = useAssistants();
const liveAssistant =
alternativeAssistant ||
selectedAssistant ||
recentAssistants[0] ||
finalAssistants[0] ||
availableAssistants[0];
@@ -737,7 +753,7 @@ export function ChatPage({
setMaxTokens(maxTokens);
}
}
refreshRecentAssistants(liveAssistant?.id);
fetchMaxTokens();
}, [liveAssistant]);
@@ -2005,48 +2021,35 @@ export function ChatPage({
!isFetchingChatMessages &&
currentSessionChatState == "input" &&
!loadingError && (
<div className="h-full flex flex-col justify-center items-center">
<div className="h-full mt-12 flex flex-col justify-center items-center">
<ChatIntro selectedPersona={liveAssistant} />
<div
key={-4}
className={`
mx-auto
px-4
w-full
max-w-[750px]
flex
flex-wrap
justify-center
mt-2
h-40
items-start
mb-6`}
>
{currentPersona?.starter_messages &&
currentPersona.starter_messages.length >
0 && (
<>
<Separator className="mx-2" />
<StarterMessages
currentPersona={currentPersona}
onSubmit={(messageOverride) =>
onSubmit({
messageOverride,
})
}
/>
{currentPersona.starter_messages
.slice(0, 4)
.map((starterMessage, i) => (
<div key={i} className="w-1/2">
<StarterMessage
starterMessage={starterMessage}
onClick={() =>
onSubmit({
messageOverride:
starterMessage.message,
})
}
/>
</div>
))}
</>
)}
</div>
{!isFetchingChatMessages &&
currentSessionChatState == "input" &&
!loadingError &&
allAssistants.length > 1 && (
<div className="mx-auto px-4 w-full max-w-[750px] flex flex-col items-center">
<Separator className="mx-2 w-full my-12" />
<div className="text-sm text-black font-medium mb-4">
Recent Assistants
</div>
<AssistantBanner
recentAssistants={recentAssistants}
liveAssistant={liveAssistant}
allAssistants={allAssistants}
onAssistantChange={onAssistantChange}
/>
</div>
)}
</div>
)}

View File

@@ -1,29 +0,0 @@
import { StarterMessage as StarterMessageType } from "../admin/assistants/interfaces";
export function StarterMessage({
starterMessage,
onClick,
}: {
starterMessage: StarterMessageType;
onClick: () => void;
}) {
return (
<div
className="mb-4 mx-2 group relative overflow-hidden rounded-xl border border-border bg-gradient-to-br from-white to-background p-4 shadow-sm transition-all duration-300 hover:shadow-md hover:scale-[1.005] cursor-pointer"
onClick={onClick}
>
<div className="absolute inset-0 bg-gradient-to-r from-blue-100 to-purple-100 opacity-0 group-hover:opacity-20 transition-opacity duration-300" />
<h3
className="text-base flex items-center font-medium text-text-800 group-hover:text-text-900 transition-colors duration-300
line-clamp-2 gap-x-2 overflow-hidden"
>
{starterMessage.name}
</h3>
<div className={`overflow-hidden transition-all duration-300 max-h-20}`}>
<p className="text-sm text-text-600 mt-2">
{starterMessage.description}
</p>
</div>
</div>
);
}

View File

@@ -0,0 +1,118 @@
import { Persona } from "../../app/admin/assistants/interfaces";
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
TooltipProvider,
} from "@/components/ui/tooltip";
export default function AssistantBanner({
recentAssistants,
liveAssistant,
allAssistants,
onAssistantChange,
}: {
recentAssistants: Persona[];
liveAssistant: Persona | undefined;
allAssistants: Persona[];
onAssistantChange: (assistant: Persona) => void;
}) {
return (
<div className="flex mx-auto mt-2 gap-4 ">
{recentAssistants
// First filter out the current assistant
.filter((assistant) => assistant.id !== liveAssistant?.id)
// Combine with visible assistants to get up to 4 total
.concat(
allAssistants.filter(
(assistant) =>
// Exclude current assistant
assistant.id !== liveAssistant?.id &&
// Exclude assistants already in recentAssistants
!recentAssistants.some((recent) => recent.id === assistant.id)
)
)
// Take first 4
.slice(0, 4)
.map((assistant) => (
<TooltipProvider key={assistant.id}>
<Tooltip>
<TooltipTrigger asChild>
<div
className="flex w-36 mx-3 py-1.5 scale-[1.] rounded-full border border-background-150 justify-center items-center gap-x-2 py-1 px-3 hover:bg-background-125 transition-colors cursor-pointer"
onClick={() => onAssistantChange(assistant)}
>
<AssistantIcon
disableToolip
size="xs"
assistant={assistant}
/>
<span className="font-semibold text-text-800 text-xs truncate max-w-[120px]">
{assistant.name}
</span>
</div>
</TooltipTrigger>
<TooltipContent backgroundColor="bg-background">
<AssistantCard assistant={assistant} />
</TooltipContent>
</Tooltip>
</TooltipProvider>
))}
</div>
);
}
export function AssistantCard({ assistant }: { assistant: Persona }) {
return (
<div className="p-6 backdrop-blur-sm rounded-lg max-w-md w-full mx-auto">
<div className="flex items-center mb-4">
<div className="mb-auto mt-2">
<AssistantIcon disableToolip size="small" assistant={assistant} />
</div>
<div className="ml-3">
<h2 className="text-lg font-semibold text-gray-800">
{assistant.name}
</h2>
<p className="text-sm text-gray-600">{assistant.description}</p>
</div>
</div>
{assistant.tools.length > 0 ||
assistant.llm_relevance_filter ||
assistant.llm_filter_extraction ? (
<div className="space-y-4">
<h3 className="text-base font-medium text-gray-800">Capabilities</h3>
<ul className="space-y-2">
{assistant.tools.map((tool, index) => (
<li
key={index}
className="flex items-center text-sm text-gray-700"
>
<span className="mr-2 text-gray-500"></span>
{tool.display_name}
</li>
))}
{assistant.llm_relevance_filter && (
<li className="flex items-center text-sm text-gray-700">
<span className="mr-2 text-gray-500"></span>
Advanced Relevance Filtering
</li>
)}
{assistant.llm_filter_extraction && (
<li className="flex items-center text-sm text-gray-700">
<span className="mr-2 text-gray-500"></span>
Smart Information Extraction
</li>
)}
</ul>
</div>
) : (
<p className="text-sm text-gray-600 italic">
No specific capabilities listed for this assistant.
</p>
)}
</div>
);
}

View File

@@ -1,5 +1,3 @@
import { CSS } from "@dnd-kit/utilities";
import { Persona } from "@/app/admin/assistants/interfaces";
import { AssistantTools } from "@/app/assistants/ToolsDisplay";
import { Bubble } from "@/components/Bubble";
@@ -115,63 +113,3 @@ export function DraggableAssistantCard(props: {
</div>
);
}
export function DisplayAssistantCard({
selectedPersona,
}: {
selectedPersona: Persona;
}) {
return (
<div className="p-4 bg-white/90 backdrop-blur-sm rounded-lg shadow-md border border-border/50 max-w-md w-full mx-auto transition-all duration-300 ease-in-out hover:shadow-lg">
<div className="flex items-center mb-3">
<AssistantIcon
disableToolip
size="medium"
assistant={selectedPersona}
/>
<h2 className="ml-3 text-xl font-semibold text-text-900">
{selectedPersona.name}
</h2>
</div>
<p className="text-sm text-text-600 mb-3 leading-relaxed">
{selectedPersona.description}
</p>
{selectedPersona.tools.length > 0 ||
selectedPersona.llm_relevance_filter ||
selectedPersona.llm_filter_extraction ? (
<div className="space-y-2">
<h3 className="text-base font-medium text-text-900">Capabilities:</h3>
<ul className="space-y-.5">
{/* display all tools */}
{selectedPersona.tools.map((tool, index) => (
<li
key={index}
className="flex items-center text-sm text-text-700"
>
<span className="mr-2 text-text-500 opacity-70"></span>{" "}
{tool.display_name}
</li>
))}
{/* Built in capabilities */}
{selectedPersona.llm_relevance_filter && (
<li className="flex items-center text-sm text-text-700">
<span className="mr-2 text-text-500 opacity-70"></span>{" "}
Advanced Relevance Filtering
</li>
)}
{selectedPersona.llm_filter_extraction && (
<li className="flex items-center text-sm text-text-700">
<span className="mr-2 text-text-500 opacity-70"></span> Smart
Information Extraction
</li>
)}
</ul>
</div>
) : (
<p className="text-sm text-text-600 italic">
No specific capabilities listed for this assistant.
</p>
)}
</div>
);
}

View File

@@ -0,0 +1,63 @@
import { Persona } from "@/app/admin/assistants/interfaces";
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
import React from "react";
export function DisplayAssistantCard({
selectedPersona,
}: {
selectedPersona: Persona;
}) {
return (
<div className="p-4 bg-white/90 backdrop-blur-sm rounded-lg shadow-md border border-border/50 max-w-md w-full mx-auto transition-all duration-300 ease-in-out hover:shadow-lg">
<div className="flex items-center mb-3">
<AssistantIcon
disableToolip
size="medium"
assistant={selectedPersona}
/>
<h2 className="ml-3 text-xl font-semibold text-text-900">
{selectedPersona.name}
</h2>
</div>
<p className="text-sm text-text-600 mb-3 leading-relaxed">
{selectedPersona.description}
</p>
{selectedPersona.tools.length > 0 ||
selectedPersona.llm_relevance_filter ||
selectedPersona.llm_filter_extraction ? (
<div className="space-y-2">
<h3 className="text-base font-medium text-text-900">Capabilities:</h3>
<ul className="space-y-.5">
{/* display all tools */}
{selectedPersona.tools.map((tool, index) => (
<li
key={index}
className="flex items-center text-sm text-text-700"
>
<span className="mr-2 text-text-500 opacity-70"></span>{" "}
{tool.display_name}
</li>
))}
{/* Built in capabilities */}
{selectedPersona.llm_relevance_filter && (
<li className="flex items-center text-sm text-text-700">
<span className="mr-2 text-text-500 opacity-70"></span>{" "}
Advanced Relevance Filtering
</li>
)}
{selectedPersona.llm_filter_extraction && (
<li className="flex items-center text-sm text-text-700">
<span className="mr-2 text-text-500 opacity-70"></span> Smart
Information Extraction
</li>
)}
</ul>
</div>
) : (
<p className="text-sm text-text-600 italic">
No specific capabilities listed for this assistant.
</p>
)}
</div>
);
}

View File

@@ -25,7 +25,7 @@ export function AssistantIcon({
disableToolip,
}: {
assistant: Persona;
size?: "small" | "medium" | "large" | "header";
size?: "xs" | "small" | "medium" | "large" | "header";
border?: boolean;
disableToolip?: boolean;
}) {
@@ -52,7 +52,9 @@ export function AssistantIcon({
? "w-14 h-14"
: size === "medium"
? "w-8 h-8"
: "w-6 h-6"
: size === "xs"
? "w-4 h-4"
: "w-6 h-6"
}`}
src={buildImgUrl(assistant.uploaded_image_id)}
loading="lazy"
@@ -68,7 +70,9 @@ export function AssistantIcon({
? "w-14 h-14"
: size === "medium"
? "w-8 h-8"
: "w-6 h-6"
: size === "xs"
? "w-4 h-4"
: "w-6 h-6"
} `}
>
{createSVG(
@@ -80,7 +84,9 @@ export function AssistantIcon({
? 56
: size === "medium"
? 32
: 24
: size === "xs"
? 16
: 24
)}
</div>
) : (
@@ -90,6 +96,7 @@ export function AssistantIcon({
${size === "large" ? "w-10 h-10" : ""}
${size === "header" ? "w-14 h-14" : ""}
${size === "medium" ? "w-8 h-8" : ""}
${size === "xs" ? "w-4 h-4" : ""}
${!size || size === "small" ? "w-6 h-6" : ""} `}
style={{ backgroundColor: color }}
/>

View File

@@ -0,0 +1,51 @@
import { useContext } from "react";
import { Persona } from "../../app/admin/assistants/interfaces";
import { SettingsContext } from "../settings/SettingsProvider";
export function StarterMessages({
currentPersona,
onSubmit,
}: {
currentPersona: Persona;
onSubmit: (messageOverride: string) => void;
}) {
const settings = useContext(SettingsContext);
const isMobile = settings?.isMobile;
return (
<div
key={-4}
className={`
mx-auto
w-full
${
isMobile
? "gap-x-2 w-2/3 justify-between"
: "justify-center max-w-[750px] items-start"
}
flex
mt-6
`}
>
{currentPersona?.starter_messages &&
currentPersona.starter_messages.length > 0 && (
<>
{currentPersona.starter_messages
.slice(0, isMobile ? 2 : 4)
.map((starterMessage, i) => (
<div key={i} className={`${isMobile ? "w-1/2" : "w-1/4"}`}>
<button
onClick={() => onSubmit(starterMessage.message)}
className={`relative flex ${
!isMobile && "w-40"
} flex-col gap-2 rounded-2xl shadow-sm border border-border px-3 py-2 text-start align-to text-wrap text-[15px] shadow-xs transition enabled:hover:bg-background-100 disabled:cursor-not-allowed line-clamp-3`}
style={{ height: `5.2rem` }}
>
{starterMessage.name}
</button>
</div>
))}
</>
)}
</div>
);
}

View File

@@ -22,7 +22,8 @@ interface AssistantsContextProps {
ownedButHiddenAssistants: Persona[];
refreshAssistants: () => Promise<void>;
isImageGenerationAvailable: boolean;
recentAssistants: Persona[];
refreshRecentAssistants: (currentAssistant: number) => Promise<void>;
// Admin only
editablePersonas: Persona[];
allAssistants: Persona[];
@@ -46,10 +47,21 @@ export const AssistantsProvider: React.FC<{
const [assistants, setAssistants] = useState<Persona[]>(
initialAssistants || []
);
const { user, isLoadingUser, isAdmin } = useUser();
const { user, isLoadingUser, refreshUser, isAdmin } = useUser();
const [editablePersonas, setEditablePersonas] = useState<Persona[]>([]);
const [allAssistants, setAllAssistants] = useState<Persona[]>([]);
const [recentAssistants, setRecentAssistants] = useState<Persona[]>(
user?.preferences.recent_assistants
?.filter((assistantId) =>
assistants.find((assistant) => assistant.id === assistantId)
)
.map(
(assistantId) =>
assistants.find((assistant) => assistant.id === assistantId)!
) || []
);
const [isImageGenerationAvailable, setIsImageGenerationAvailable] =
useState<boolean>(false);
@@ -98,6 +110,28 @@ export const AssistantsProvider: React.FC<{
fetchPersonas();
}, [isAdmin]);
const refreshRecentAssistants = async (currentAssistant: number) => {
const response = await fetch("/api/user/recent-assistants", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
current_assistant: currentAssistant,
}),
});
if (!response.ok) {
return;
}
setRecentAssistants((recentAssistants) => [
assistants.find((assistant) => assistant.id === currentAssistant)!,
...recentAssistants.filter(
(assistant) => assistant.id !== currentAssistant
),
]);
};
const refreshAssistants = async () => {
try {
const response = await fetch("/api/persona", {
@@ -125,6 +159,12 @@ export const AssistantsProvider: React.FC<{
} catch (error) {
console.error("Error refreshing assistants:", error);
}
setRecentAssistants(
assistants.filter(
(assistant) =>
user?.preferences.recent_assistants?.includes(assistant.id) || false
)
);
};
const {
@@ -167,6 +207,8 @@ export const AssistantsProvider: React.FC<{
editablePersonas,
allAssistants,
isImageGenerationAvailable,
recentAssistants,
refreshRecentAssistants,
}}
>
{children}

View File

@@ -203,7 +203,7 @@ export default function CreateCredential({
for information on setting up this connector.
</p>
)}
<CardSection className="!border-0 mt-4 flex flex-col gap-y-6">
<CardSection className="w-full !border-0 mt-4 flex flex-col gap-y-6">
<TextFormField
name="name"
placeholder="(Optional) credential name.."

View File

@@ -471,9 +471,9 @@ export function HorizontalSourceSelector({
icon={<FiBook size={16} />}
defaultDisplay="Sets"
resetValues={resetDocuments}
width="w-fit max-w-24 ellipsis truncate"
width="w-fit max-w-24 etext-llipsis truncate"
dropdownWidth="max-w-36 w-fit"
optionClassName="truncate break-all ellipsis"
optionClassName="truncate w-full break-all"
/>
)}
@@ -506,7 +506,7 @@ export function HorizontalSourceSelector({
resetValues={resetTags}
width="w-fit max-w-24 ellipsis truncate"
dropdownWidth="max-w-80 w-fit"
optionClassName="truncate break-all ellipsis"
optionClassName="truncate w-full break-all ellipsis"
/>
)}
</div>

View File

@@ -14,13 +14,16 @@ const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content> & {
maxWidth?: string;
backgroundColor?: string;
}
>(({ className, sideOffset = 4, maxWidth, ...props }, ref) => (
>(({ className, sideOffset = 4, maxWidth, backgroundColor, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md border border-neutral-200 text-white bg-background-900 px-2 py-1.5 text-sm shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50 max-w-sm",
`z-50 overflow-hidden rounded-md border border-neutral-200 text-white ${
backgroundColor || "bg-background-900"
} px-2 py-1.5 text-sm shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50 max-w-sm`,
className
)}
{...props}

View File

@@ -8,6 +8,7 @@ interface UserPreferences {
visible_assistants: number[];
hidden_assistants: number[];
default_model: string | null;
recent_assistants: number[];
}
export enum UserStatus {