-
Notifications
You must be signed in to change notification settings - Fork 116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add ability to edit a conversation title #1921
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,12 +2,17 @@ import { | |
ArrowUpOnSquareIcon, | ||
Avatar, | ||
Button, | ||
CheckIcon, | ||
ClipboardCheckIcon, | ||
DropdownMenu, | ||
IconButton, | ||
LinkStrokeIcon, | ||
PencilSquareIcon, | ||
TrashIcon, | ||
XMarkIcon, | ||
} from "@dust-tt/sparkle"; | ||
import React, { useState } from "react"; | ||
import React, { MouseEvent, useRef, useState } from "react"; | ||
import { useSWRConfig } from "swr"; | ||
|
||
import { useConversation } from "@app/lib/swr"; | ||
import { WorkspaceType } from "@app/types/user"; | ||
|
@@ -23,7 +28,14 @@ export function ConversationTitle({ | |
shareLink: string; | ||
onDelete?: () => void; | ||
}) { | ||
const { mutate } = useSWRConfig(); | ||
|
||
const [copyLinkSuccess, setCopyLinkSuccess] = useState<boolean>(false); | ||
const [isEditingTitle, setIsEditingTitle] = useState<boolean>(true); | ||
const [editedTitle, setEditedTitle] = useState<string>(""); | ||
|
||
const titleInputFocused = useRef(false); | ||
const saveButtonFocused = useRef(false); | ||
|
||
const handleClick = async () => { | ||
await navigator.clipboard.writeText(shareLink || ""); | ||
|
@@ -39,16 +51,116 @@ export function ConversationTitle({ | |
workspaceId: owner.sId, | ||
}); | ||
|
||
const onTitleChange = async (title: string) => { | ||
try { | ||
const res = await fetch( | ||
`/api/w/${owner.sId}/assistant/conversations/${conversationId}`, | ||
{ | ||
method: "PATCH", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify({ | ||
title, | ||
visibility: conversation?.visibility, | ||
}), | ||
} | ||
); | ||
await mutate( | ||
`/api/w/${owner.sId}/assistant/conversations/${conversationId}` | ||
); | ||
void mutate(`/api/w/${owner.sId}/assistant/conversations`); | ||
if (!res.ok) { | ||
throw new Error("Failed to update title"); | ||
} | ||
setIsEditingTitle(false); | ||
setEditedTitle(""); | ||
} catch (e) { | ||
alert("Failed to update title"); | ||
} | ||
}; | ||
|
||
if (isConversationLoading || isConversationError || !conversation) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<div className="grid h-full max-w-full grid-cols-[1fr,auto] items-center gap-4"> | ||
<div className="overflow-hidden truncate"> | ||
<span className="font-bold">{conversation?.title || ""}</span> | ||
</div> | ||
<div className="grid h-full min-w-0 max-w-full grid-cols-[1fr,auto] items-center gap-4"> | ||
<div className="flex min-w-0 flex-row items-center gap-4"> | ||
{!isEditingTitle ? ( | ||
<div className="min-w-0 overflow-hidden truncate"> | ||
<span className="font-bold">{conversation?.title || ""}</span> | ||
</div> | ||
) : ( | ||
<div className="flex-grow"> | ||
<input | ||
className="w-full rounded-md font-bold" | ||
value={editedTitle} | ||
onChange={(e) => setEditedTitle(e.target.value)} | ||
// We need to make sure the click on the save button below | ||
// is registered before the onBlur event, so we keep track of the | ||
// focus state of both the input and the save button. | ||
onFocus={() => (titleInputFocused.current = true)} | ||
onBlur={() => { | ||
setTimeout(() => { | ||
if (!saveButtonFocused.current) { | ||
setIsEditingTitle(false); | ||
} | ||
titleInputFocused.current = false; | ||
}, 0); | ||
}} | ||
onKeyDown={(e) => { | ||
if (e.key === "Enter") { | ||
return onTitleChange(editedTitle); | ||
} | ||
}} | ||
autoFocus | ||
/> | ||
</div> | ||
)} | ||
|
||
{isEditingTitle ? ( | ||
<div className="flex flex-row"> | ||
<div | ||
onClick={(e: MouseEvent<HTMLDivElement>) => { | ||
e.preventDefault(); | ||
return onTitleChange(editedTitle); | ||
}} | ||
// See comment on the input above. | ||
onFocus={() => (saveButtonFocused.current = true)} | ||
onBlur={() => { | ||
setTimeout(() => { | ||
if (!titleInputFocused.current) { | ||
setIsEditingTitle(false); | ||
} | ||
saveButtonFocused.current = false; | ||
}, 0); | ||
}} | ||
className="flex items-center" | ||
> | ||
<IconButton icon={CheckIcon} variant="secondary" /> | ||
</div> | ||
<IconButton | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The alignment is not perfect here the icon is a bit above the text? Can we tweak it a bit? Also that gray looks not super sharp maybe we can use the flavour that is darker? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed |
||
icon={XMarkIcon} | ||
onClick={() => { | ||
setIsEditingTitle(false); | ||
setEditedTitle(""); | ||
}} | ||
variant="secondary" | ||
/> | ||
</div> | ||
) : ( | ||
<IconButton | ||
icon={PencilSquareIcon} | ||
onClick={() => { | ||
setEditedTitle(conversation?.title || ""); | ||
setIsEditingTitle(true); | ||
}} | ||
size="sm" | ||
variant="secondary" | ||
/> | ||
)} | ||
</div> | ||
<div className="flex items-center gap-6"> | ||
<div className="hidden lg:flex"> | ||
<Avatar.Stack | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You want to mutate conversations as well to update the left-hand pane?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. That's a whole lot of refetching for just a title :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done