Allow users to share assistants (#2434)

* enable assistant sharing

* functional

* remove logs

* revert ports

* remove accidental update

* minor updates to copy

* update formatting

* update for merge queue
This commit is contained in:
pablodanswer
2024-09-16 18:35:29 -07:00
committed by GitHub
parent 7ba829a585
commit 7f7559e3d2
11 changed files with 238 additions and 49 deletions

View File

@@ -8,7 +8,11 @@ import { usePopup } from "@/components/admin/connectors/Popup";
import { useState, useMemo, useEffect } from "react";
import { UniqueIdentifier } from "@dnd-kit/core";
import { DraggableTable } from "@/components/table/DraggableTable";
import { deletePersona, personaComparator } from "./lib";
import {
deletePersona,
personaComparator,
togglePersonaVisibility,
} from "./lib";
import { FiEdit2 } from "react-icons/fi";
import { TrashIcon } from "@/components/icons/icons";
import { getCurrentUser } from "@/lib/user";
@@ -31,22 +35,6 @@ function PersonaTypeDisplay({ persona }: { persona: Persona }) {
return <Text>Personal {persona.owner && <>({persona.owner.email})</>}</Text>;
}
const togglePersonaVisibility = async (
personaId: number,
isVisible: boolean
) => {
const response = await fetch(`/api/admin/persona/${personaId}/visible`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
is_visible: !isVisible,
}),
});
return response;
};
export function PersonasTable({
allPersonas,
editablePersonas,

View File

@@ -320,6 +320,38 @@ export function personaComparator(a: Persona, b: Persona) {
return closerToZeroNegativesFirstComparator(a.id, b.id);
}
export const togglePersonaVisibility = async (
personaId: number,
isVisible: boolean
) => {
const response = await fetch(`/api/persona/${personaId}/visible`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
is_visible: !isVisible,
}),
});
return response;
};
export const togglePersonaPublicStatus = async (
personaId: number,
isPublic: boolean
) => {
const response = await fetch(`/api/persona/${personaId}/public`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
is_public: isPublic,
}),
});
return response;
};
export function checkPersonaRequiresImageGeneration(persona: Persona) {
for (const tool of persona.tools) {
if (tool.name === "ImageGenerationTool") {

View File

@@ -1,14 +1,7 @@
import { User } from "@/lib/types";
import { Persona } from "../admin/assistants/interfaces";
import { checkUserOwnsAssistant } from "@/lib/assistants/checkOwnership";
import {
FiImage,
FiLock,
FiMoreHorizontal,
FiSearch,
FiUnlock,
} from "react-icons/fi";
import { CustomTooltip } from "@/components/tooltip/CustomTooltip";
import { FiLock, FiUnlock } from "react-icons/fi";
export function AssistantSharedStatusDisplay({
assistant,
@@ -56,20 +49,6 @@ 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

@@ -6,11 +6,15 @@ import { Persona } from "@/app/admin/assistants/interfaces";
import { Divider, Text } from "@tremor/react";
import {
FiEdit2,
FiFigma,
FiMenu,
FiMinus,
FiMoreHorizontal,
FiPlus,
FiSearch,
FiShare,
FiShare2,
FiToggleLeft,
FiTrash,
FiX,
} from "react-icons/fi";
@@ -50,11 +54,14 @@ import {
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { DragHandle } from "@/components/table/DragHandle";
import { deletePersona } from "@/app/admin/assistants/lib";
import {
deletePersona,
togglePersonaPublicStatus,
} from "@/app/admin/assistants/lib";
import { DeleteEntityModal } from "@/components/modals/DeleteEntityModal";
import { MakePublicAssistantModal } from "@/app/chat/modal/MakePublicAssistantModal";
function DraggableAssistantListItem(props: any) {
const {
@@ -81,7 +88,7 @@ function DraggableAssistantListItem(props: any) {
<DragHandle />
</div>
<div className="flex-grow">
<AssistantListItem del {...props} />
<AssistantListItem {...props} />
</div>
</div>
);
@@ -95,6 +102,7 @@ function AssistantListItem({
isVisible,
setPopup,
deleteAssistant,
shareAssistant,
}: {
assistant: Persona;
user: User | null;
@@ -102,7 +110,7 @@ function AssistantListItem({
allAssistantIds: string[];
isVisible: boolean;
deleteAssistant: Dispatch<SetStateAction<Persona | null>>;
shareAssistant: Dispatch<SetStateAction<Persona | null>>;
setPopup: (popupSpec: PopupSpec | null) => void;
}) {
const router = useRouter();
@@ -258,6 +266,18 @@ function AssistantListItem({
) : (
<></>
),
isOwnedByUser ? (
<div
key="delete"
className="flex items-center gap-x-2"
onClick={() => shareAssistant(assistant)}
>
{assistant.is_public ? <FiMinus /> : <FiPlus />} Make{" "}
{assistant.is_public ? "Private" : "Public"}
</div>
) : (
<></>
),
]}
</DefaultPopover>
</div>
@@ -290,6 +310,9 @@ export function AssistantsList({
assistant.id.toString()
);
const [deletingPersona, setDeletingPersona] = useState<Persona | null>(null);
const [makePublicPersona, setMakePublicPersona] = useState<Persona | null>(
null
);
const { popup, setPopup } = usePopup();
const router = useRouter();
@@ -307,7 +330,7 @@ export function AssistantsList({
async function handleDragEnd(event: DragEndEvent) {
const { active, over } = event;
filteredAssistants;
if (over && active.id !== over.id) {
setFilteredAssistants((assistants) => {
const oldIndex = assistants.findIndex(
@@ -351,6 +374,20 @@ export function AssistantsList({
/>
)}
{makePublicPersona && (
<MakePublicAssistantModal
isPublic={makePublicPersona.is_public}
onClose={() => setMakePublicPersona(null)}
onShare={async (newPublicStatus: boolean) => {
await togglePersonaPublicStatus(
makePublicPersona.id,
newPublicStatus
);
router.refresh();
}}
/>
)}
<div className="mx-auto mobile:w-[90%] desktop:w-searchbar-xs 2xl:w-searchbar-sm 3xl:w-searchbar">
<AssistantsPageTitle>My Assistants</AssistantsPageTitle>
@@ -403,6 +440,7 @@ export function AssistantsList({
{filteredAssistants.map((assistant, index) => (
<DraggableAssistantListItem
deleteAssistant={setDeletingPersona}
shareAssistant={setMakePublicPersona}
key={assistant.id}
assistant={assistant}
user={user}
@@ -431,6 +469,7 @@ export function AssistantsList({
{ownedButHiddenAssistants.map((assistant, index) => (
<AssistantListItem
deleteAssistant={setDeletingPersona}
shareAssistant={setMakePublicPersona}
key={assistant.id}
assistant={assistant}
user={user}

View File

@@ -0,0 +1,72 @@
import { ModalWrapper } from "@/components/modals/ModalWrapper";
import { Button, Divider, Text } from "@tremor/react";
export function MakePublicAssistantModal({
isPublic,
onShare,
onClose,
}: {
isPublic: boolean;
onShare: (shared: boolean) => void;
onClose: () => void;
}) {
return (
<ModalWrapper onClose={onClose} modalClassName="max-w-3xl">
<div className="space-y-6">
<h2 className="text-2xl font-bold text-emphasis">
{isPublic ? "Public Assistant" : "Make Assistant Public"}
</h2>
<Text>
This assistant is currently{" "}
<span className="font-semibold">
{isPublic ? "public" : "private"}
</span>
.
{isPublic
? " Anyone can currently access this assistant."
: " Only you can access this assistant."}
</Text>
<Divider />
{isPublic ? (
<div className="space-y-4">
<Text>
To restrict access to this assistant, you can make it private
again.
</Text>
<Button
onClick={async () => {
await onShare?.(false);
onClose();
}}
size="sm"
color="red"
>
Make Assistant Private
</Button>
</div>
) : (
<div className="space-y-4">
<Text>
Making this assistant public will allow anyone with the link to
view and use it. Ensure that all content and capabilities of the
assistant are safe to share.
</Text>
<Button
onClick={async () => {
await onShare?.(true);
onClose();
}}
size="sm"
color="green"
>
Make Assistant Public
</Button>
</div>
)}
</div>
</ModalWrapper>
);
}