mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-09-19 12:03:54 +02:00
Ux improvements (#3947)
* black history sidebar * misc improvements * minor misc ux improvemnts * quick nit * add nits * quick nit
This commit is contained in:
@@ -228,6 +228,7 @@ def create_update_persona(
|
||||
num_chunks=create_persona_request.num_chunks,
|
||||
llm_relevance_filter=create_persona_request.llm_relevance_filter,
|
||||
llm_filter_extraction=create_persona_request.llm_filter_extraction,
|
||||
is_default_persona=create_persona_request.is_default_persona,
|
||||
)
|
||||
|
||||
versioned_make_persona_private = fetch_versioned_implementation(
|
||||
|
@@ -15,8 +15,6 @@ import {
|
||||
PopoverContent,
|
||||
} from "@/components/ui/popover";
|
||||
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
|
||||
import { AssistantVisibilityPopover } from "./AssistantVisibilityPopover";
|
||||
import { DeleteAssistantPopover } from "./DeleteAssistantPopover";
|
||||
import { Persona } from "@/app/admin/assistants/interfaces";
|
||||
import { useUser } from "@/components/user/UserProvider";
|
||||
import { useAssistants } from "@/components/context/AssistantsContext";
|
||||
@@ -149,14 +147,9 @@ const AssistantCard: React.FC<{
|
||||
)}
|
||||
</div>
|
||||
{isOwnedByUser && (
|
||||
<div className="flex ml-2 items-center gap-x-2">
|
||||
<Popover
|
||||
open={activePopover !== undefined}
|
||||
onOpenChange={(open) =>
|
||||
open ? setActivePopover(null) : setActivePopover(undefined)
|
||||
}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<div className="flex ml-2 relative items-center gap-x-2">
|
||||
<Popover modal>
|
||||
<PopoverTrigger>
|
||||
<button
|
||||
type="button"
|
||||
className="hover:bg-neutral-200 dark:hover:bg-neutral-700 p-1 -my-1 rounded-full"
|
||||
@@ -165,86 +158,56 @@ const AssistantCard: React.FC<{
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className={`z-[1000000] ${
|
||||
activePopover === null ? "w-32" : "w-80"
|
||||
} p-2`}
|
||||
className={`w-32 z-[10000] p-2 hover:bg-red-400`}
|
||||
>
|
||||
{activePopover === null && (
|
||||
<div className="flex flex-col text-sm space-y-1">
|
||||
<div className="flex flex-col text-sm space-y-1">
|
||||
<button
|
||||
onClick={isOwnedByUser ? handleEdit : undefined}
|
||||
className={`w-full flex items-center text-left px-2 py-1 rounded ${
|
||||
isOwnedByUser
|
||||
? "hover:bg-neutral-200 dark:hover:bg-neutral-700"
|
||||
: "opacity-50 cursor-not-allowed"
|
||||
}`}
|
||||
disabled={!isOwnedByUser}
|
||||
>
|
||||
<FiEdit size={12} className="inline mr-2" />
|
||||
Edit
|
||||
</button>
|
||||
{isPaidEnterpriseFeaturesEnabled && isOwnedByUser && (
|
||||
<button
|
||||
onClick={isOwnedByUser ? handleEdit : undefined}
|
||||
className={`w-full flex items-center text-left px-2 py-1 rounded ${
|
||||
onClick={
|
||||
isOwnedByUser
|
||||
? "hover:bg-neutral-200 dark:hover:bg-neutral-700"
|
||||
? () => {
|
||||
router.push(
|
||||
`/assistants/stats/${persona.id}`
|
||||
);
|
||||
closePopover();
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
className={`w-full text-left items-center px-2 py-1 rounded ${
|
||||
isOwnedByUser
|
||||
? "hover:bg-neutral-200 dark:hover:bg-neutral-800"
|
||||
: "opacity-50 cursor-not-allowed"
|
||||
}`}
|
||||
disabled={!isOwnedByUser}
|
||||
>
|
||||
<FiEdit size={12} className="inline mr-2" />
|
||||
Edit
|
||||
<FiBarChart size={12} className="inline mr-2" />
|
||||
Stats
|
||||
</button>
|
||||
{isPaidEnterpriseFeaturesEnabled && isOwnedByUser && (
|
||||
<button
|
||||
onClick={
|
||||
isOwnedByUser
|
||||
? () => {
|
||||
router.push(
|
||||
`/assistants/stats/${persona.id}`
|
||||
);
|
||||
closePopover();
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
className={`w-full text-left items-center px-2 py-1 rounded ${
|
||||
isOwnedByUser
|
||||
? "hover:bg-neutral-200 dark:hover:bg-neutral-800"
|
||||
: "opacity-50 cursor-not-allowed"
|
||||
}`}
|
||||
disabled={!isOwnedByUser}
|
||||
>
|
||||
<FiBarChart size={12} className="inline mr-2" />
|
||||
Stats
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={isOwnedByUser ? handleDelete : undefined}
|
||||
className={`w-full text-left items-center px-2 py-1 rounded ${
|
||||
isOwnedByUser
|
||||
? "hover:bg-neutral-200 dark:hover:bg-neutral- text-red-600 dark:text-red-400"
|
||||
: "opacity-50 cursor-not-allowed text-red-300 dark:text-red-500"
|
||||
}`}
|
||||
disabled={!isOwnedByUser}
|
||||
>
|
||||
<FiTrash size={12} className="inline mr-2" />
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{activePopover === "visibility" && (
|
||||
<AssistantVisibilityPopover
|
||||
assistant={persona}
|
||||
user={user}
|
||||
allUsers={[]}
|
||||
onClose={closePopover}
|
||||
onTogglePublic={async (isPublic: boolean) => {
|
||||
await togglePersonaPublicStatus(persona.id, isPublic);
|
||||
await refreshAssistants();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{activePopover === "delete" && (
|
||||
<DeleteAssistantPopover
|
||||
entityName={persona.name}
|
||||
onClose={closePopover}
|
||||
onSubmit={async () => {
|
||||
const success = await deletePersona(persona.id);
|
||||
if (success) {
|
||||
await refreshAssistants();
|
||||
}
|
||||
closePopover();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
)}
|
||||
<button
|
||||
onClick={isOwnedByUser ? handleDelete : undefined}
|
||||
className={`w-full text-left items-center px-2 py-1 rounded ${
|
||||
isOwnedByUser
|
||||
? "hover:bg-neutral-200 dark:hover:bg-neutral- text-red-600 dark:text-red-400"
|
||||
: "opacity-50 cursor-not-allowed text-red-300 dark:text-red-500"
|
||||
}`}
|
||||
disabled={!isOwnedByUser}
|
||||
>
|
||||
<FiTrash size={12} className="inline mr-2" />
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
|
@@ -118,7 +118,7 @@ export function AssistantModal({
|
||||
return (
|
||||
<Dialog open={true} onOpenChange={(open) => !open && hideModal()}>
|
||||
<DialogContent
|
||||
className="p-0 max-h-[80vh] max-w-none w-[95%] bg-background rounded-sm shadow-2xl transform transition-all duration-300 ease-in-out relative w-11/12 max-w-4xl pt-10 pb-10 px-10 overflow-hidden flex flex-col max-w-4xl"
|
||||
className="p-0 max-h-[80vh] max-w-none w-[95%] bg-background rounded-sm shadow-2xl transform transition-all duration-300 ease-in-out relative w-11/12 max-w-4xl pt-10 pb-10 px-10 flex flex-col max-w-4xl"
|
||||
style={{
|
||||
position: "fixed",
|
||||
top: "10vh",
|
||||
@@ -127,7 +127,7 @@ export function AssistantModal({
|
||||
margin: 0,
|
||||
}}
|
||||
>
|
||||
<div className="flex den flex-col h-full">
|
||||
<div className="flex overflow-hidden flex-col h-full">
|
||||
<div className="flex flex-col sticky top-0 z-10">
|
||||
<div className="flex px-2 justify-between items-center gap-x-2 mb-0">
|
||||
<div className="h-12 w-full rounded-lg flex-col justify-center items-start gap-2.5 inline-flex">
|
||||
@@ -205,7 +205,7 @@ export function AssistantModal({
|
||||
Featured Assistants
|
||||
</h2>
|
||||
|
||||
<div className="w-full px-2 pb-2 grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-6">
|
||||
<div className="w-full px-2 pb-10 grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-6">
|
||||
{featuredAssistants.length > 0 ? (
|
||||
featuredAssistants.map((assistant, index) => (
|
||||
<div key={index}>
|
||||
|
@@ -1,198 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import { MinimalUserSnapshot, User } from "@/lib/types";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { FiPlus, FiX, FiEye, FiEyeOff } from "react-icons/fi";
|
||||
import { SearchMultiSelectDropdown } from "@/components/Dropdown";
|
||||
import { UsersIcon } from "@/components/icons/icons";
|
||||
import { AssistantSharedStatusDisplay } from "../AssistantSharedStatus";
|
||||
import {
|
||||
addUsersToAssistantSharedList,
|
||||
removeUsersFromAssistantSharedList,
|
||||
} from "@/lib/assistants/shareAssistant";
|
||||
import { usePopup } from "@/components/admin/connectors/Popup";
|
||||
import { Bubble } from "@/components/Bubble";
|
||||
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
|
||||
import { Spinner } from "@/components/Spinner";
|
||||
import { useAssistants } from "@/components/context/AssistantsContext";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Persona } from "@/app/admin/assistants/interfaces";
|
||||
import { ThreeDotsLoader } from "@/components/Loading";
|
||||
|
||||
interface AssistantVisibilityPopoverProps {
|
||||
assistant: Persona;
|
||||
user: User | null;
|
||||
allUsers: MinimalUserSnapshot[];
|
||||
onClose: () => void;
|
||||
onTogglePublic: (isPublic: boolean) => Promise<void>;
|
||||
}
|
||||
|
||||
export function AssistantVisibilityPopover({
|
||||
assistant,
|
||||
user,
|
||||
allUsers,
|
||||
onClose,
|
||||
onTogglePublic,
|
||||
}: AssistantVisibilityPopoverProps) {
|
||||
const { refreshAssistants } = useAssistants();
|
||||
const { popup, setPopup } = usePopup();
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const [selectedUsers, setSelectedUsers] = useState<MinimalUserSnapshot[]>([]);
|
||||
|
||||
const assistantName = assistant.name;
|
||||
const sharedUsersWithoutOwner = (assistant.users || [])?.filter(
|
||||
(u: MinimalUserSnapshot) => u.id !== assistant.owner?.id
|
||||
);
|
||||
|
||||
const handleShare = async () => {
|
||||
setIsUpdating(true);
|
||||
const startTime = Date.now();
|
||||
|
||||
const error = await addUsersToAssistantSharedList(
|
||||
assistant,
|
||||
selectedUsers.map((user) => user.id)
|
||||
);
|
||||
await refreshAssistants();
|
||||
|
||||
const elapsedTime = Date.now() - startTime;
|
||||
const remainingTime = Math.max(0, 1000 - elapsedTime);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsUpdating(false);
|
||||
if (error) {
|
||||
setPopup({
|
||||
message: `Failed to share assistant - ${error}`,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
}, remainingTime);
|
||||
};
|
||||
|
||||
const handleTogglePublic = async () => {
|
||||
setIsUpdating(true);
|
||||
await onTogglePublic(!assistant.is_public);
|
||||
setIsUpdating(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{popup}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold mb-2">Visibility</h3>
|
||||
<Button
|
||||
onClick={handleTogglePublic}
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full justify-start"
|
||||
>
|
||||
{assistant.is_public ? (
|
||||
<>
|
||||
<FiEyeOff className="mr-2" />
|
||||
Make Private
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FiEye className="mr-2" />
|
||||
Make Public
|
||||
</>
|
||||
)}
|
||||
{isUpdating && (
|
||||
<div className="ml-2 inline-flex items-center">
|
||||
<ThreeDotsLoader />
|
||||
<span className="ml-2 text-sm text-text-600">Updating...</span>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold mb-2">Share</h3>
|
||||
<SearchMultiSelectDropdown
|
||||
options={allUsers
|
||||
.filter(
|
||||
(u1) =>
|
||||
!selectedUsers.map((u2) => u2.id).includes(u1.id) &&
|
||||
!sharedUsersWithoutOwner
|
||||
.map((u2: MinimalUserSnapshot) => u2.id)
|
||||
.includes(u1.id) &&
|
||||
u1.id !== user?.id
|
||||
)
|
||||
.map((user) => ({
|
||||
name: user.email,
|
||||
value: user.id,
|
||||
}))}
|
||||
onSelect={(option) => {
|
||||
setSelectedUsers([
|
||||
...Array.from(
|
||||
new Set([
|
||||
...selectedUsers,
|
||||
{ id: option.value as string, email: option.name },
|
||||
])
|
||||
),
|
||||
]);
|
||||
}}
|
||||
itemComponent={({ option }) => (
|
||||
<div className="flex items-center px-4 py-2.5 cursor-pointer hover:bg-background-100">
|
||||
<UsersIcon className="mr-3 text-text-500" />
|
||||
<span className="flex-grow">{option.name}</span>
|
||||
<FiPlus className="text-blue-500" />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{selectedUsers.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-xs font-medium text-text-700 mb-2">
|
||||
Selected Users:
|
||||
</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{selectedUsers.map((selectedUser) => (
|
||||
<div
|
||||
key={selectedUser.id}
|
||||
onClick={() => {
|
||||
setSelectedUsers(
|
||||
selectedUsers.filter(
|
||||
(user) => user.id !== selectedUser.id
|
||||
)
|
||||
);
|
||||
}}
|
||||
className="flex items-center bg-blue-50 text-blue-700 rounded-full px-3 py-1 text-xs hover:bg-blue-100 transition-colors duration-200 cursor-pointer"
|
||||
>
|
||||
{selectedUser.email}
|
||||
<FiX className="ml-2 text-blue-500" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{selectedUsers.length > 0 && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
handleShare();
|
||||
setSelectedUsers([]);
|
||||
}}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
>
|
||||
Share with Selected Users
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold mb-2">Currently Shared With</h3>
|
||||
<div className="bg-background-50 rounded-lg p-2">
|
||||
<AssistantSharedStatusDisplay
|
||||
size="md"
|
||||
assistant={assistant}
|
||||
user={user}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
@@ -1,31 +0,0 @@
|
||||
import React from "react";
|
||||
import { FiTrash } from "react-icons/fi";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface DeleteAssistantPopoverProps {
|
||||
entityName: string;
|
||||
onClose: () => void;
|
||||
onSubmit: () => void;
|
||||
}
|
||||
|
||||
export function DeleteAssistantPopover({
|
||||
entityName,
|
||||
onClose,
|
||||
onSubmit,
|
||||
}: DeleteAssistantPopoverProps) {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<p className="text-sm mb-3">
|
||||
Are you sure you want to delete assistant <b>{entityName}</b>?
|
||||
</p>
|
||||
<div className="flex justify-center gap-2">
|
||||
<Button variant="secondary" size="sm" onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="destructive" size="sm" onClick={onSubmit}>
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@@ -162,12 +162,12 @@ export const FolderDropdown = forwardRef<HTMLDivElement, FolderDropdownProps>(
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
<div
|
||||
className="sticky top-0 bg-background-sidebar z-10"
|
||||
className="sticky top-0 bg-background-sidebar dark:bg-transparent z-10"
|
||||
style={{ zIndex: 1000 - index }}
|
||||
>
|
||||
<div
|
||||
ref={ref}
|
||||
className="flex overflow-visible items-center w-full text-text-darker rounded-md p-1 relative bg-background-sidebar sticky top-0"
|
||||
className="flex overflow-visible items-center w-full text-text-darker rounded-md p-1 relative sticky top-0"
|
||||
style={{ zIndex: 10 - index }}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
|
@@ -161,7 +161,6 @@ export default function LLMPopover({
|
||||
size: 16,
|
||||
className: "flex-none my-auto text-black",
|
||||
})}
|
||||
asdfasdf
|
||||
<span className="line-clamp-1 ">
|
||||
{getDisplayNameForModel(name)}
|
||||
</span>
|
||||
|
@@ -273,6 +273,7 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
|
||||
border-r
|
||||
dark:border-none
|
||||
dark:text-[#D4D4D4]
|
||||
dark:bg-[#000]
|
||||
border-sidebar-border
|
||||
flex
|
||||
flex-col relative
|
||||
|
@@ -317,7 +317,7 @@ export function PagesTab({
|
||||
return (
|
||||
<div className="flex flex-col gap-y-2 flex-grow">
|
||||
{popup}
|
||||
<div className="px-4 mt-2 group mr-2 bg-background-sidebar z-20">
|
||||
<div className="px-4 mt-2 group mr-2 bg-background-sidebar dark:bg-transparent z-20">
|
||||
<div className="flex justify-between text-sm gap-x-2 text-text-300/80 items-center font-normal leading-normal">
|
||||
<p>Chats</p>
|
||||
<button
|
||||
|
@@ -116,7 +116,7 @@ export function Modal({
|
||||
{icon && icon({ size: 30 })}
|
||||
</h2>
|
||||
</div>
|
||||
{!hideDividerForTitle && <Separator />}
|
||||
{!hideDividerForTitle ? <Separator /> : <div className="my-4" />}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@@ -2,7 +2,7 @@ import "./spinner.css";
|
||||
|
||||
export const Spinner = () => {
|
||||
return (
|
||||
<div className="fixed top-0 left-0 z-50 w-screen h-screen bg-black bg-opacity-50 flex items-center justify-center">
|
||||
<div className="fixed top-0 left-0 z-50 w-screen h-screen bg-[#000] bg-opacity-50 flex items-center justify-center">
|
||||
<div className="loader ease-linear rounded-full border-8 border-t-8 border-background-200 h-8 w-8"></div>
|
||||
</div>
|
||||
);
|
||||
|
@@ -75,7 +75,7 @@ export function ClientLayout({
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="default-scrollbar flex-none text-text-settings-sidebar bg-background-sidebar w-[250px] overflow-x-hidden z-20 pt-2 pb-8 h-full border-r border-border dark:border-none miniscroll overflow-auto">
|
||||
<div className="default-scrollbar flex-none text-text-settings-sidebar bg-background-sidebar dark:bg-[#000] w-[250px] overflow-x-hidden z-20 pt-2 pb-8 h-full border-r border-border dark:border-none miniscroll overflow-auto">
|
||||
<AdminSidebar
|
||||
collections={[
|
||||
{
|
||||
|
@@ -19,7 +19,7 @@ const PopoverContent = React.forwardRef<
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-[1000000] w-72 rounded-md border border-neutral-200 bg-white p-4 text-neutral-950 shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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",
|
||||
" z-50 w-72 rounded-md border border-neutral-200 bg-white p-4 text-neutral-950 shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-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",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
@@ -647,6 +647,7 @@ export const useUserGroups = (): {
|
||||
|
||||
const MODEL_DISPLAY_NAMES: { [key: string]: string } = {
|
||||
// OpenAI models
|
||||
"o1-2025-12-17": "O1 (December 2025)",
|
||||
"o3-mini": "O3 Mini",
|
||||
"o1-mini": "O1 Mini",
|
||||
"o1-preview": "O1 Preview",
|
||||
|
Reference in New Issue
Block a user