diff --git a/api/chatbot/models.py b/api/chatbot/models.py index eaab0bcc..6046e104 100644 --- a/api/chatbot/models.py +++ b/api/chatbot/models.py @@ -8,6 +8,7 @@ class Conversation(JsonModel): title: str owner: str = Field(index=True) + pinned: bool = False created_at: datetime = Field(default_factory=utcnow) updated_at: datetime = created_at diff --git a/api/chatbot/routers/conversation.py b/api/chatbot/routers/conversation.py index f80c9044..03a794c0 100644 --- a/api/chatbot/routers/conversation.py +++ b/api/chatbot/routers/conversation.py @@ -28,7 +28,7 @@ async def get_conversations( userid: Annotated[str | None, UserIdHeader()] = None ) -> list[Conversation]: convs = await ORMConversation.find(ORMConversation.owner == userid).all() - convs.sort(key=lambda x: x.updated_at, reverse=True) + convs.sort(key=lambda x: (x.pinned, x.updated_at), reverse=True) return [Conversation(**conv.dict()) for conv in convs] @@ -72,9 +72,16 @@ async def update_conversation( payload: UpdateConversation, userid: Annotated[str | None, UserIdHeader()] = None, ) -> ConversationDetail: + modified = False conv = await ORMConversation.get(conversation_id) - conv.title = payload.title - await conv.save() + if payload.title is not None: + conv.title = payload.title + modified = True + if payload.pinned is not None: + conv.pinned = payload.pinned + modified = True + if modified: + await conv.save() return ConversationDetail(**conv.dict()) diff --git a/api/chatbot/schemas.py b/api/chatbot/schemas.py index df64224e..2e8f676f 100644 --- a/api/chatbot/schemas.py +++ b/api/chatbot/schemas.py @@ -70,6 +70,7 @@ class Conversation(BaseModel): id: Optional[str] = None title: str owner: str + pinned: bool = False created_at: datetime = Field(default_factory=utcnow) updated_at: datetime = created_at @@ -92,7 +93,8 @@ class CreateConversation(BaseModel): class UpdateConversation(BaseModel): - title: str + title: Optional[str] = None + pinned: Optional[bool] = None class UserProfile(BaseModel): diff --git a/web/src/routes/conversation/index.jsx b/web/src/routes/conversation/index.jsx index 012d120c..3a30ddc9 100644 --- a/web/src/routes/conversation/index.jsx +++ b/web/src/routes/conversation/index.jsx @@ -21,6 +21,7 @@ export async function action({ params, request }) { }, body: JSON.stringify({ title: conversation.title, + pinned: conversation.pinned, }), }) if (!resp.ok) { diff --git a/web/src/routes/root/SideMenuButton/index.jsx b/web/src/routes/root/SideMenuButton/index.jsx index e3886221..93f16122 100644 --- a/web/src/routes/root/SideMenuButton/index.jsx +++ b/web/src/routes/root/SideMenuButton/index.jsx @@ -1,13 +1,14 @@ import "./index.css"; import { useState, useRef } from "react"; -import { useSubmit } from "react-router-dom"; +import { useFetcher } from "react-router-dom"; import Tooltip from "@mui/material/Tooltip"; +import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome'; import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; import DriveFileRenameOutlineIcon from "@mui/icons-material/DriveFileRenameOutline"; import MoreVertIcon from '@mui/icons-material/MoreVert'; -import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome'; +import PushPinOutlinedIcon from '@mui/icons-material/PushPinOutlined'; import { DropdownMenu, DropdownHeader, DropdownList } from "components/DropdownMenu"; import { summarizeConversation } from "requests"; @@ -21,7 +22,7 @@ import { summarizeConversation } from "requests"; * @returns */ const ChatTab = ({ chat, isActive, onDeleteClick }) => { - const submit = useSubmit(); + const fetcher = useFetcher(); const titleRef = useRef(null); const [titleEditable, setTitleEditable] = useState("false"); @@ -36,8 +37,8 @@ const ChatTab = ({ chat, isActive, onDeleteClick }) => { const renameChat = async (title) => { setTitleEditable("false"); - submit( - { title: title }, + fetcher.submit( + { ...chat, title: title }, { method: "put", action: `/conversations/${chat.id}`, encType: "application/json" } ); // Maybe set snackbar to inform user? @@ -63,6 +64,13 @@ const ChatTab = ({ chat, isActive, onDeleteClick }) => { }); } + const flipPin = () => { + fetcher.submit( + { ...chat, pinned: !chat.pinned }, + { method: "put", action: `/conversations/${chat.id}`, encType: "application/json" } + ); + }; + return ( <> @@ -84,6 +92,14 @@ const ChatTab = ({ chat, isActive, onDeleteClick }) => { +
  • + +