Skip to content

Commit

Permalink
Merge pull request #298 from Sema4AI/fix-pr-297
Browse files Browse the repository at this point in the history
Fix pr 297
  • Loading branch information
mkorpela authored Apr 14, 2024
2 parents 028ed27 + df9e4da commit 65c67db
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 5 deletions.
10 changes: 10 additions & 0 deletions backend/app/api/threads.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,13 @@ async def upsert_thread(
assistant_id=thread_put_request.assistant_id,
name=thread_put_request.name,
)


@router.delete("/{tid}")
async def delete_thread(
user: AuthedUser,
tid: ThreadID,
):
"""Delete a thread by ID."""
await storage.delete_thread(user["user_id"], tid)
return {"status": "ok"}
10 changes: 10 additions & 0 deletions backend/app/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,3 +171,13 @@ async def get_or_create_user(sub: str) -> tuple[User, bool]:
'INSERT INTO "user" (sub) VALUES ($1) RETURNING *', sub
)
return user, True


async def delete_thread(user_id: str, thread_id: str):
"""Delete a thread by ID."""
async with get_pg_pool().acquire() as conn:
await conn.execute(
"DELETE FROM thread WHERE thread_id = $1 AND user_id = $2",
thread_id,
user_id,
)
3 changes: 2 additions & 1 deletion frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { useThreadAndAssistant } from "./hooks/useThreadAndAssistant.ts";
function App(props: { edit?: boolean }) {
const navigate = useNavigate();
const [sidebarOpen, setSidebarOpen] = useState(false);
const { chats, createChat } = useChatList();
const { chats, createChat, deleteChat } = useChatList();
const { configs, saveConfig } = useConfigList();
const { startStream, stopStream, stream } = useStreamState();
const { configSchema, configDefaults } = useSchemas();
Expand Down Expand Up @@ -117,6 +117,7 @@ function App(props: { edit?: boolean }) {
<ChatList
chats={chats}
enterChat={selectChat}
deleteChat={deleteChat}
enterConfig={selectConfig}
/>
}
Expand Down
58 changes: 54 additions & 4 deletions frontend/src/components/ChatList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PlusIcon } from "@heroicons/react/24/outline";
import { PlusIcon, EllipsisVerticalIcon } from "@heroicons/react/24/outline";
import { useState, useEffect } from "react";

import { ChatListProps } from "../hooks/useChatList";
import { cn } from "../utils/cn";
Expand All @@ -7,10 +8,21 @@ import { useThreadAndAssistant } from "../hooks/useThreadAndAssistant.ts";
export function ChatList(props: {
chats: ChatListProps["chats"];
enterChat: (id: string | null) => void;
deleteChat: (id: string) => void;
enterConfig: (id: string | null) => void;
}) {
const { currentChat, assistantConfig } = useThreadAndAssistant();

// State for tracking which chat's menu is visible
const [visibleMenu, setVisibleMenu] = useState<string | null>(null);

// Event listener to close the menu when clicking outside of it
useEffect(() => {
const closeMenu = () => setVisibleMenu(null);
window.addEventListener("click", closeMenu);
return () => window.removeEventListener("click", closeMenu);
}, []);

return (
<>
<div
Expand Down Expand Up @@ -62,14 +74,17 @@ export function ChatList(props: {
</div>
<ul role="list" className="-mx-2 mt-2 space-y-1">
{props.chats?.map((chat) => (
<li key={chat.thread_id}>
<li
key={chat.thread_id}
className="flex justify-between items-center p-2 rounded-md hover:bg-gray-50 cursor-pointer"
>
<div
onClick={() => props.enterChat(chat.thread_id)}
className={cn(
chat.thread_id === currentChat?.thread_id
? "bg-gray-50 text-indigo-600"
: "text-gray-700 hover:text-indigo-600 hover:bg-gray-50",
"group flex gap-x-3 rounded-md p-2 leading-6 cursor-pointer",
"group flex gap-x-3 rounded-md p-2 leading-6 cursor-pointer flex-grow min-w-0",
)}
>
<span
Expand All @@ -82,8 +97,43 @@ export function ChatList(props: {
>
{chat.name?.[0] ?? " "}
</span>
<span className="truncate">{chat.name}</span>
<span className="truncate flex-grow min-w-0">{chat.name}</span>
</div>
{/* Ellipsis Button */}
<button
onClick={(event) => {
event.stopPropagation(); // Prevent triggering click for the chat item
setVisibleMenu(
visibleMenu === chat.thread_id ? null : chat.thread_id,
);
}}
className="p-1 rounded-full hover:bg-gray-200"
>
<EllipsisVerticalIcon className="h-5 w-5" />
</button>
{/* Menu Dropdown */}
{visibleMenu === chat.thread_id && (
<div className="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none z-10">
<div
className="py-1"
role="menu"
aria-orientation="vertical"
aria-labelledby="options-menu"
>
<a
href="#"
className="text-gray-700 block px-4 py-2 text-sm hover:bg-gray-100"
role="menuitem"
onClick={(event) => {
event.preventDefault();
props.deleteChat(chat.thread_id);
}}
>
Delete
</a>
</div>
</div>
)}
</li>
)) ?? (
<li className="leading-6 p-2 animate-pulse font-black text-gray-400 text-lg">
Expand Down
15 changes: 15 additions & 0 deletions frontend/src/hooks/useChatList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export interface ChatListProps {
assistant_id: string,
thread_id?: string,
) => Promise<Chat>;
deleteChat: (thread_id: string) => Promise<void>;
}

function chatsReducer(
Expand Down Expand Up @@ -87,8 +88,22 @@ export function useChatList(): ChatListProps {
return saved;
}, []);

const deleteChat = useCallback(
async (thread_id: string) => {
await fetch(`/threads/${thread_id}`, {
method: "DELETE",
headers: {
Accept: "application/json",
},
});
setChats((chats || []).filter((c: Chat) => c.thread_id !== thread_id));
},
[chats],
);

return {
chats,
createChat,
deleteChat,
};
}

0 comments on commit 65c67db

Please sign in to comment.