mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-04-02 08:58:11 +02:00
Better assistant interactions + UI (#2029)
* add assistnat re-ordering, selections, etc. * squash * remove unnecessary comment * squash * adapt dragging for all IDs + smoother animation + consistency * fix minor typing issue * fix minor typing issue * remove logs
This commit is contained in:
parent
cd22cca4e8
commit
5825d01d53
@ -248,7 +248,6 @@ def create_initial_default_connector(db_session: Session) -> None:
|
||||
default_connector_id = 0
|
||||
default_connector = fetch_connector_by_id(default_connector_id, db_session)
|
||||
if default_connector is not None:
|
||||
# Check if the existing connector has the correct values
|
||||
if (
|
||||
default_connector.source != DocumentSource.INGESTION_API
|
||||
or default_connector.input_type != InputType.LOAD_STATE
|
||||
|
@ -211,7 +211,7 @@ export default function AddConnector({
|
||||
}
|
||||
|
||||
// Without credential
|
||||
if (isSuccess && response && credentialActivated) {
|
||||
if (credentialActivated && isSuccess && response) {
|
||||
const credential =
|
||||
currentCredential || liveGDriveCredential || liveGmailCredential;
|
||||
const linkCredentialResponse = await linkCredential(
|
||||
@ -228,7 +228,19 @@ export default function AddConnector({
|
||||
setTimeout(() => {
|
||||
window.open("/admin/indexing/status", "_self");
|
||||
}, 1000);
|
||||
} else {
|
||||
const errorData = await linkCredentialResponse.json();
|
||||
setPopup({
|
||||
message: errorData.message,
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
} else if (isSuccess) {
|
||||
setPopup({
|
||||
message:
|
||||
"Credential created succsfully! Redirecting to connector home page",
|
||||
type: "success",
|
||||
});
|
||||
} else {
|
||||
setPopup({ message: message, type: "error" });
|
||||
}
|
||||
|
@ -250,10 +250,21 @@ const DynamicConnectionForm: React.FC<DynamicConnectionFormProps> = ({
|
||||
{field.description}
|
||||
</CredentialSubText>
|
||||
)}
|
||||
|
||||
<Field
|
||||
onChange={(
|
||||
e: React.ChangeEvent<HTMLSelectElement>
|
||||
) =>
|
||||
updateValue(setFieldValue)(
|
||||
field.name,
|
||||
e.target.value
|
||||
)
|
||||
}
|
||||
as="select"
|
||||
value={values[field.name]}
|
||||
name={field.name}
|
||||
className="w-full p-2 border bg-input border-border-medium rounded-md focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
|
||||
className="w-full p-2 border bg-input border-border-medium rounded-md bg-black
|
||||
focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"
|
||||
>
|
||||
<option value="">Select an option</option>
|
||||
{field.options?.map((option) => (
|
||||
|
@ -102,6 +102,7 @@ export function AssistantsGallery({
|
||||
my-auto
|
||||
ml-2
|
||||
text-strong
|
||||
line-clamp-2
|
||||
"
|
||||
>
|
||||
{assistant.name}
|
||||
|
@ -4,25 +4,10 @@ import { useState } from "react";
|
||||
import { MinimalUserSnapshot, User } from "@/lib/types";
|
||||
import { Persona } from "@/app/admin/assistants/interfaces";
|
||||
import { Divider, Text } from "@tremor/react";
|
||||
import {
|
||||
FiArrowDown,
|
||||
FiArrowUp,
|
||||
FiEdit2,
|
||||
FiMoreHorizontal,
|
||||
FiPlus,
|
||||
FiSearch,
|
||||
FiX,
|
||||
FiShare2,
|
||||
FiImage,
|
||||
} from "react-icons/fi";
|
||||
import { FiEdit2, FiPlus, FiSearch, FiShare2 } from "react-icons/fi";
|
||||
import Link from "next/link";
|
||||
import { orderAssistantsForUser } from "@/lib/assistants/orderAssistants";
|
||||
import {
|
||||
addAssistantToList,
|
||||
moveAssistantDown,
|
||||
moveAssistantUp,
|
||||
removeAssistantFromList,
|
||||
} from "@/lib/assistants/updateAssistantPreferences";
|
||||
import { updateUserAssistantList } from "@/lib/assistants/updateAssistantPreferences";
|
||||
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
|
||||
import { DefaultPopover } from "@/components/popover/DefaultPopover";
|
||||
import { PopupSpec, usePopup } from "@/components/admin/connectors/Popup";
|
||||
@ -34,8 +19,56 @@ import { AssistantSharingModal } from "./AssistantSharingModal";
|
||||
import { AssistantSharedStatusDisplay } from "../AssistantSharedStatus";
|
||||
import useSWR from "swr";
|
||||
import { errorHandlingFetcher } from "@/lib/fetcher";
|
||||
import { AssistantTools, ToolsDisplay } from "../ToolsDisplay";
|
||||
import { CustomTooltip } from "@/components/tooltip/CustomTooltip";
|
||||
import { AssistantTools } from "../ToolsDisplay";
|
||||
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
DragEndEvent,
|
||||
} from "@dnd-kit/core";
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { useSortable } from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
|
||||
import { DragHandle } from "@/components/table/DragHandle";
|
||||
|
||||
function DraggableAssistantListItem(props: any) {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({ id: props.assistant.id.toString() });
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.9 : 1,
|
||||
zIndex: isDragging ? 1000 : "auto",
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={setNodeRef} style={style} className="flex items-center">
|
||||
<div {...attributes} {...listeners} className="mr-2 cursor-grab">
|
||||
<DragHandle />
|
||||
</div>
|
||||
<div className="flex-grow">
|
||||
<AssistantListItem {...props} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function AssistantListItem({
|
||||
assistant,
|
||||
@ -50,7 +83,7 @@ function AssistantListItem({
|
||||
assistant: Persona;
|
||||
user: User | null;
|
||||
allUsers: MinimalUserSnapshot[];
|
||||
allAssistantIds: number[];
|
||||
allAssistantIds: string[];
|
||||
isFirst: boolean;
|
||||
isLast: boolean;
|
||||
isVisible: boolean;
|
||||
@ -59,7 +92,6 @@ function AssistantListItem({
|
||||
const router = useRouter();
|
||||
const [showSharingModal, setShowSharingModal] = useState(false);
|
||||
|
||||
const currentChosenAssistants = user?.preferences?.chosen_assistants;
|
||||
const isOwnedByUser = checkUserOwnsAssistant(user, assistant);
|
||||
|
||||
return (
|
||||
@ -83,7 +115,6 @@ function AssistantListItem({
|
||||
>
|
||||
<div
|
||||
className="
|
||||
|
||||
flex
|
||||
justify-between
|
||||
items-center
|
||||
@ -92,13 +123,13 @@ function AssistantListItem({
|
||||
<div className="w-3/4">
|
||||
<div className="flex items-center">
|
||||
<AssistantIcon assistant={assistant} />
|
||||
<h2 className="text-xl font-semibold my-auto ml-2">
|
||||
<h2 className="text-xl line-clamp-2 font-semibold my-auto ml-2">
|
||||
{assistant.name}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div className="text-sm mt-2">{assistant.description}</div>
|
||||
<div className="mt-2 flex items-center gap-x-3">
|
||||
<div className="mt-2 flex items-start gap-y-2 flex-col gap-x-3">
|
||||
<AssistantSharedStatusDisplay assistant={assistant} user={user} />
|
||||
{assistant.tools.length != 0 && (
|
||||
<AssistantTools list assistant={assistant} />
|
||||
@ -111,7 +142,10 @@ function AssistantListItem({
|
||||
{!assistant.is_public && (
|
||||
<div
|
||||
className="mr-4 rounded p-2 cursor-pointer hover:bg-hover"
|
||||
onClick={() => setShowSharingModal(true)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setShowSharingModal(true);
|
||||
}}
|
||||
>
|
||||
<FiShare2 size={16} />
|
||||
</div>
|
||||
@ -124,158 +158,31 @@ function AssistantListItem({
|
||||
</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;
|
||||
}
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
interface AssistantsListProps {
|
||||
export function AssistantsList({
|
||||
user,
|
||||
assistants,
|
||||
}: {
|
||||
user: User | null;
|
||||
assistants: Persona[];
|
||||
}
|
||||
}) {
|
||||
const [filteredAssistants, setFilteredAssistants] = useState(
|
||||
orderAssistantsForUser(assistants, user)
|
||||
);
|
||||
|
||||
export function AssistantsList({ user, assistants }: AssistantsListProps) {
|
||||
const filteredAssistants = orderAssistantsForUser(assistants, user);
|
||||
const ownedButHiddenAssistants = assistants.filter(
|
||||
(assistant) =>
|
||||
checkUserOwnsAssistant(user, assistant) &&
|
||||
user?.preferences?.chosen_assistants &&
|
||||
!user?.preferences?.chosen_assistants?.includes(assistant.id)
|
||||
);
|
||||
const allAssistantIds = assistants.map((assistant) => assistant.id);
|
||||
const allAssistantIds = assistants.map((assistant) =>
|
||||
assistant.id.toString()
|
||||
);
|
||||
|
||||
const { popup, setPopup } = usePopup();
|
||||
|
||||
@ -284,6 +191,32 @@ export function AssistantsList({ user, assistants }: AssistantsListProps) {
|
||||
errorHandlingFetcher
|
||||
);
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
);
|
||||
|
||||
async function handleDragEnd(event: DragEndEvent) {
|
||||
const { active, over } = event;
|
||||
|
||||
if (over && active.id !== over.id) {
|
||||
setFilteredAssistants((assistants) => {
|
||||
const oldIndex = assistants.findIndex(
|
||||
(a) => a.id.toString() === active.id
|
||||
);
|
||||
const newIndex = assistants.findIndex(
|
||||
(a) => a.id.toString() === over.id
|
||||
);
|
||||
const newAssistants = arrayMove(assistants, oldIndex, newIndex);
|
||||
|
||||
updateUserAssistantList(newAssistants.map((a) => a.id));
|
||||
return newAssistants;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{popup}
|
||||
@ -323,24 +256,35 @@ export function AssistantsList({ user, assistants }: AssistantsListProps) {
|
||||
<Text>
|
||||
The order the assistants appear below will be the order they appear in
|
||||
the Assistants dropdown. The first assistant listed will be your
|
||||
default assistant when you start a new chat.
|
||||
default assistant when you start a new chat. Drag and drop to reorder.
|
||||
</Text>
|
||||
|
||||
<div className="w-full p-4 mt-3">
|
||||
{filteredAssistants.map((assistant, index) => (
|
||||
<AssistantListItem
|
||||
key={assistant.id}
|
||||
assistant={assistant}
|
||||
user={user}
|
||||
allAssistantIds={allAssistantIds}
|
||||
allUsers={users || []}
|
||||
isFirst={index === 0}
|
||||
isLast={index === filteredAssistants.length - 1}
|
||||
isVisible
|
||||
setPopup={setPopup}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<SortableContext
|
||||
items={filteredAssistants.map((a) => a.id.toString())}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
<div className="w-full p-4 mt-3">
|
||||
{filteredAssistants.map((assistant, index) => (
|
||||
<DraggableAssistantListItem
|
||||
key={assistant.id}
|
||||
assistant={assistant}
|
||||
user={user}
|
||||
allAssistantIds={allAssistantIds}
|
||||
allUsers={users || []}
|
||||
isFirst={false}
|
||||
isLast={index === filteredAssistants.length - 1}
|
||||
isVisible
|
||||
setPopup={setPopup}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
|
||||
{ownedButHiddenAssistants.length > 0 && (
|
||||
<>
|
||||
@ -362,7 +306,7 @@ export function AssistantsList({ user, assistants }: AssistantsListProps) {
|
||||
allAssistantIds={allAssistantIds}
|
||||
allUsers={users || []}
|
||||
isFirst={index === 0}
|
||||
isLast={index === filteredAssistants.length - 1}
|
||||
isLast={index === ownedButHiddenAssistants.length - 1}
|
||||
isVisible={false}
|
||||
setPopup={setPopup}
|
||||
/>
|
||||
|
@ -1,16 +1,9 @@
|
||||
import { HistorySidebar } from "@/app/chat/sessionSidebar/HistorySidebar";
|
||||
import { InstantSSRAutoRefresh } from "@/components/SSRAutoRefresh";
|
||||
import { UserDropdown } from "@/components/UserDropdown";
|
||||
import { ChatProvider } from "@/components/context/ChatContext";
|
||||
import { WelcomeModal } from "@/components/initialSetup/welcome/WelcomeModalWrapper";
|
||||
import { ApiKeyModal } from "@/components/llm/ApiKeyModal";
|
||||
import { fetchChatData } from "@/lib/chat/fetchChatData";
|
||||
import { unstable_noStore as noStore } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import { AssistantsList } from "./AssistantsList";
|
||||
import { Logo } from "@/components/Logo";
|
||||
import FixedLogo from "@/app/chat/shared_chat_search/FixedLogo";
|
||||
import SidebarWrapper from "../SidebarWrapper";
|
||||
import WrappedAssistantsMine from "./WrappedAssistantsMine";
|
||||
|
||||
export default async function GalleryPage({
|
||||
|
@ -55,7 +55,6 @@ import { ShareChatSessionModal } from "./modal/ShareChatSessionModal";
|
||||
import { FiArrowDown } from "react-icons/fi";
|
||||
import { ChatIntro } from "./ChatIntro";
|
||||
import { AIMessage, HumanMessage } from "./message/Messages";
|
||||
import { ThreeDots } from "react-loader-spinner";
|
||||
import { StarterMessage } from "./StarterMessage";
|
||||
import { AnswerPiecePacket, DanswerDocument } from "@/lib/search/interfaces";
|
||||
import { buildFilters } from "@/lib/search/utils";
|
||||
@ -1169,6 +1168,7 @@ export function ChatPage({
|
||||
>
|
||||
<div className="w-full relative">
|
||||
<HistorySidebar
|
||||
reset={() => setMessage("")}
|
||||
page="chat"
|
||||
ref={innerSidebarElementRef}
|
||||
toggleSidebar={toggleSidebar}
|
||||
@ -1192,6 +1192,7 @@ export function ChatPage({
|
||||
<div className="flex h-full flex-col w-full">
|
||||
{liveAssistant && (
|
||||
<FunctionalHeader
|
||||
reset={() => setMessage("")}
|
||||
page="chat"
|
||||
setSharingModalVisible={
|
||||
chatSessionIdRef.current !== null
|
||||
|
@ -1,108 +1,92 @@
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
DragEndEvent,
|
||||
} from "@dnd-kit/core";
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
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, { useState } from "react";
|
||||
import { FiBookmark, FiPlus } from "react-icons/fi";
|
||||
|
||||
interface AssistantsTabProps {
|
||||
selectedAssistant: Persona;
|
||||
availableAssistants: Persona[];
|
||||
llmProviders: LLMProviderDescriptor[];
|
||||
onSelect: (assistant: Persona) => void;
|
||||
}
|
||||
import { updateUserAssistantList } from "@/lib/assistants/updateAssistantPreferences";
|
||||
import { DraggableAssistantCard } from "@/components/assistants/AssistantCards";
|
||||
|
||||
export function AssistantsTab({
|
||||
selectedAssistant,
|
||||
availableAssistants,
|
||||
llmProviders,
|
||||
onSelect,
|
||||
}: AssistantsTabProps) {
|
||||
}: {
|
||||
selectedAssistant: Persona;
|
||||
availableAssistants: Persona[];
|
||||
llmProviders: LLMProviderDescriptor[];
|
||||
onSelect: (assistant: Persona) => void;
|
||||
}) {
|
||||
const [_, llmName] = getFinalLLM(llmProviders, null, null);
|
||||
const [assistants, setAssistants] = useState(availableAssistants);
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
);
|
||||
|
||||
function handleDragEnd(event: DragEndEvent) {
|
||||
const { active, over } = event;
|
||||
|
||||
if (over && active.id !== over.id) {
|
||||
setAssistants((items) => {
|
||||
const oldIndex = items.findIndex(
|
||||
(item) => item.id.toString() === active.id
|
||||
);
|
||||
const newIndex = items.findIndex(
|
||||
(item) => item.id.toString() === over.id
|
||||
);
|
||||
const updatedAssistants = arrayMove(items, oldIndex, newIndex);
|
||||
|
||||
updateUserAssistantList(updatedAssistants.map((a) => a.id));
|
||||
|
||||
return updatedAssistants;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="py-4">
|
||||
<h3 className="px-4 text-lg font-semibold">Change Assistant</h3>
|
||||
<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) => (
|
||||
<AssistantCard
|
||||
key={assistant.id}
|
||||
assistant={assistant}
|
||||
isSelected={selectedAssistant.id === assistant.id}
|
||||
onSelect={onSelect}
|
||||
llmName={llmName}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<SortableContext
|
||||
items={assistants.map((a) => a.id.toString())}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
<div className="px-2 pb-2 mx-2 max-h-[500px] miniscroll overflow-y-scroll my-3 grid grid-cols-1 gap-4">
|
||||
{assistants.map((assistant) => (
|
||||
<DraggableAssistantCard
|
||||
key={assistant.id.toString()}
|
||||
assistant={assistant}
|
||||
isSelected={selectedAssistant.id === assistant.id}
|
||||
onSelect={onSelect}
|
||||
llmName={llmName}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
</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 text-wrap 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>
|
||||
);
|
||||
};
|
||||
|
@ -53,11 +53,13 @@ interface HistorySidebarProps {
|
||||
toggleSidebar?: () => void;
|
||||
toggled?: boolean;
|
||||
removeToggle?: () => void;
|
||||
reset?: () => void;
|
||||
}
|
||||
|
||||
export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
|
||||
(
|
||||
{
|
||||
reset = () => null,
|
||||
toggled,
|
||||
page,
|
||||
existingChats,
|
||||
@ -69,7 +71,6 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
|
||||
},
|
||||
ref: ForwardedRef<HTMLDivElement>
|
||||
) => {
|
||||
const commandSymbol = KeyboardSymbol();
|
||||
const router = useRouter();
|
||||
const { popup, setPopup } = usePopup();
|
||||
|
||||
@ -85,9 +86,19 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
|
||||
if (!combinedSettings) {
|
||||
return null;
|
||||
}
|
||||
const settings = combinedSettings.settings;
|
||||
|
||||
const enterpriseSettings = combinedSettings.enterpriseSettings;
|
||||
|
||||
const handleNewChat = () => {
|
||||
reset();
|
||||
const newChatUrl =
|
||||
`/${page}` +
|
||||
(NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA && currentChatSession
|
||||
? `?assistantId=${currentChatSession.persona_id}`
|
||||
: "");
|
||||
router.push(newChatUrl);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{popup}
|
||||
@ -144,19 +155,13 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
|
||||
|
||||
{page == "chat" && (
|
||||
<div className="mx-3 mt-4 gap-y-1 flex-col flex gap-x-1.5 items-center items-center">
|
||||
<Link
|
||||
href={
|
||||
"/chat" +
|
||||
(NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA &&
|
||||
currentChatSession
|
||||
? `?assistantId=${currentChatSession.persona_id}`
|
||||
: "")
|
||||
}
|
||||
<button
|
||||
onClick={handleNewChat}
|
||||
className="w-full p-2 bg-white border-border border rounded items-center hover:bg-background-200 cursor-pointer transition-all duration-150 flex gap-x-2"
|
||||
>
|
||||
<FiEdit className="flex-none " />
|
||||
<p className="my-auto flex items-center text-sm">New Chat</p>
|
||||
</Link>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() =>
|
||||
|
112
web/src/components/assistants/AssistantCards.tsx
Normal file
112
web/src/components/assistants/AssistantCards.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
|
||||
import { Persona } from "@/app/admin/assistants/interfaces";
|
||||
import { AssistantTools } from "@/app/assistants/ToolsDisplay";
|
||||
import { Bubble } from "@/components/Bubble";
|
||||
import { AssistantIcon } from "@/components/assistants/AssistantIcon";
|
||||
import { getDisplayNameForModel } from "@/lib/hooks";
|
||||
import { useSortable } from "@dnd-kit/sortable";
|
||||
import React, { useState } from "react";
|
||||
import { FiBookmark } from "react-icons/fi";
|
||||
import { MdDragIndicator } from "react-icons/md";
|
||||
|
||||
export 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)}
|
||||
className={`
|
||||
p-4
|
||||
cursor-pointer
|
||||
border
|
||||
${isSelected ? "bg-hover" : "hover:bg-hover-light"}
|
||||
shadow-md
|
||||
rounded-lg
|
||||
border-border
|
||||
max-w-full
|
||||
flex items-center
|
||||
overflow-x-hidden
|
||||
`}
|
||||
onMouseEnter={() => setHovering(true)}
|
||||
onMouseLeave={() => setHovering(false)}
|
||||
>
|
||||
<div className="flex-grow">
|
||||
<div className="flex items-center mb-2">
|
||||
<AssistantIcon assistant={assistant} />
|
||||
<div className="ml-2 ellipsis font-bold text-sm text-emphasis">
|
||||
{assistant.name}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-wrap 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>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export function DraggableAssistantCard(props: {
|
||||
assistant: Persona;
|
||||
isSelected: boolean;
|
||||
onSelect: (assistant: Persona) => void;
|
||||
llmName: string;
|
||||
}) {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({ id: props.assistant.id.toString() });
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.9 : 1,
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={setNodeRef} style={style} className="flex items-center">
|
||||
<div {...attributes} {...listeners} className="mr-1 cursor-grab">
|
||||
<MdDragIndicator className="h-3 w-3 flex-none" />
|
||||
</div>
|
||||
<div className="flex-grow">
|
||||
<AssistantCard {...props} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -16,6 +16,7 @@ import KeyboardSymbol from "@/lib/browserUtilities";
|
||||
import Link from "next/link";
|
||||
import { SettingsContext } from "../settings/SettingsProvider";
|
||||
import { pageType } from "@/app/chat/sessionSidebar/types";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
export default function FunctionalHeader({
|
||||
user,
|
||||
@ -23,7 +24,9 @@ export default function FunctionalHeader({
|
||||
currentChatSession,
|
||||
setSharingModalVisible,
|
||||
toggleSidebar,
|
||||
reset = () => null,
|
||||
}: {
|
||||
reset?: () => void;
|
||||
page: pageType;
|
||||
user: User | null;
|
||||
currentChatSession?: ChatSession | null | undefined;
|
||||
@ -58,6 +61,17 @@ export default function FunctionalHeader({
|
||||
window.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, []);
|
||||
const router = useRouter();
|
||||
|
||||
const handleNewChat = () => {
|
||||
reset();
|
||||
const newChatUrl =
|
||||
`/${page}` +
|
||||
(NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA && currentChatSession
|
||||
? `?assistantId=${currentChatSession.persona_id}`
|
||||
: "");
|
||||
router.push(newChatUrl);
|
||||
};
|
||||
return (
|
||||
<div className="pb-6 left-0 sticky top-0 z-20 w-full relative flex">
|
||||
<div className="mt-2 mx-4 text-text-700 flex w-full">
|
||||
@ -86,21 +100,12 @@ export default function FunctionalHeader({
|
||||
</div>
|
||||
|
||||
{page == "chat" && (
|
||||
<Tooltip delayDuration={1000} content={`${commandSymbol}U`}>
|
||||
<Link
|
||||
className="mobile:hidden my-auto"
|
||||
href={
|
||||
`/${page}` +
|
||||
(NEXT_PUBLIC_NEW_CHAT_DIRECTS_TO_SAME_PERSONA &&
|
||||
currentChatSession
|
||||
? `?assistantId=${currentChatSession.persona_id}`
|
||||
: "")
|
||||
}
|
||||
>
|
||||
<Tooltip delayDuration={1000} content="New Chat">
|
||||
<button className="mobile:hidden my-auto" onClick={handleNewChat}>
|
||||
<div className="cursor-pointer ml-2 flex-none text-text-700 hover:text-text-600 transition-colors duration-300">
|
||||
<NewChatIcon size={20} />
|
||||
</div>
|
||||
</Link>
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
|
@ -541,6 +541,7 @@ export const SearchSection = ({
|
||||
>
|
||||
<div className="w-full relative">
|
||||
<HistorySidebar
|
||||
reset={() => setQuery("")}
|
||||
page="search"
|
||||
ref={innerSidebarElementRef}
|
||||
toggleSidebar={toggleSidebar}
|
||||
@ -552,6 +553,7 @@ export const SearchSection = ({
|
||||
|
||||
<div className="absolute left-0 w-full top-0">
|
||||
<FunctionalHeader
|
||||
reset={() => setQuery("")}
|
||||
toggleSidebar={toggleSidebar}
|
||||
page="search"
|
||||
user={user}
|
||||
|
@ -4,9 +4,7 @@ import {
|
||||
TableRow,
|
||||
TableHeaderCell,
|
||||
TableBody,
|
||||
TableCell,
|
||||
} from "@tremor/react";
|
||||
import { DraggableTableBody } from "./DraggableTableBody";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import {
|
||||
closestCenter,
|
||||
|
@ -1,4 +1,4 @@
|
||||
async function updateUserAssistantList(
|
||||
export async function updateUserAssistantList(
|
||||
chosenAssistants: number[]
|
||||
): Promise<boolean> {
|
||||
const response = await fetch("/api/user/assistant-list", {
|
||||
|
@ -213,7 +213,6 @@ export async function fetchChatData(searchParams: {
|
||||
)
|
||||
);
|
||||
}
|
||||
// // TODO check for image capabilities and enable if so
|
||||
|
||||
let folders: Folder[] = [];
|
||||
if (foldersResponse?.ok) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user