Download frontend/src/components/Sidebar/ActiveWorkspaces/ThreadContainer/ThreadItem/index.jsx from leowuming/rag: direct link, hf CLI and curl.
- Browser
- Download file 8.32 kB
-
https://huggingface.co/spaces/leowuming/rag/resolve/main/frontend/src/components/Sidebar/ActiveWorkspaces/ThreadContainer/ThreadItem/index.jsx
- Command line
-
hf download hf://spaces/leowuming/rag/frontend/src/components/Sidebar/ActiveWorkspaces/ThreadContainer/ThreadItem/index.jsx
-
curl -L -o index.jsx https://huggingface.co/spaces/leowuming/rag/resolve/main/frontend/src/components/Sidebar/ActiveWorkspaces/ThreadContainer/ThreadItem/index.jsx
8.32 kB
| import Workspace from "@/models/workspace"; | |
| import paths from "@/utils/paths"; | |
| import showToast from "@/utils/toast"; | |
| import { | |
| ArrowCounterClockwise, | |
| DotsThree, | |
| PencilSimple, | |
| Trash, | |
| X, | |
| } from "@phosphor-icons/react"; | |
| import { useEffect, useRef, useState } from "react"; | |
| import { useParams } from "react-router-dom"; | |
| const THREAD_CALLOUT_DETAIL_WIDTH = 26; | |
| export default function ThreadItem({ | |
| idx, | |
| activeIdx, | |
| isActive, | |
| workspace, | |
| thread, | |
| onRemove, | |
| toggleMarkForDeletion, | |
| hasNext, | |
| ctrlPressed = false, | |
| }) { | |
| const { slug, threadSlug = null } = useParams(); | |
| const optionsContainer = useRef(null); | |
| const [showOptions, setShowOptions] = useState(false); | |
| const linkTo = !thread.slug | |
| ? paths.workspace.chat(slug) | |
| : paths.workspace.thread(slug, thread.slug); | |
| return ( | |
| <div | |
| className="w-full relative flex h-[38px] items-center border-none rounded-lg" | |
| role="listitem" | |
| > | |
| {/* Curved line Element and leader if required */} | |
| <div | |
| style={{ width: THREAD_CALLOUT_DETAIL_WIDTH / 2 }} | |
| className={`${ | |
| isActive | |
| ? "border-l-2 border-b-2 border-white light:border-theme-sidebar-border z-[2]" | |
| : "border-l border-b border-[#6F6F71] light:border-theme-sidebar-border z-[1]" | |
| } h-[50%] absolute top-0 left-3 rounded-bl-lg`} | |
| ></div> | |
| {/* Downstroke border for next item */} | |
| {hasNext && ( | |
| <div | |
| style={{ width: THREAD_CALLOUT_DETAIL_WIDTH / 2 }} | |
| className={`${ | |
| idx <= activeIdx && !isActive | |
| ? "border-l-2 border-white light:border-theme-sidebar-border z-[2]" | |
| : "border-l border-[#6F6F71] light:border-theme-sidebar-border z-[1]" | |
| } h-[100%] absolute top-0 left-3`} | |
| ></div> | |
| )} | |
| {/* Curved line inline placeholder for spacing - not visible */} | |
| <div | |
| style={{ width: THREAD_CALLOUT_DETAIL_WIDTH + 8 }} | |
| className="h-full" | |
| /> | |
| <div | |
| className={`flex w-full items-center justify-between pr-2 group relative ${isActive ? "bg-[var(--theme-sidebar-thread-selected)] border border-solid border-transparent light:border-blue-400" : "hover:bg-theme-sidebar-subitem-hover"} rounded-[4px]`} | |
| > | |
| {thread.deleted ? ( | |
| <div className="w-full flex justify-between"> | |
| <div className="w-full pl-2 py-1"> | |
| <p | |
| className={`text-left text-sm text-slate-400/50 light:text-slate-500 italic`} | |
| > | |
| deleted thread | |
| </p> | |
| </div> | |
| {ctrlPressed && ( | |
| <button | |
| type="button" | |
| className="border-none" | |
| onClick={() => toggleMarkForDeletion(thread.id)} | |
| > | |
| <ArrowCounterClockwise | |
| className="text-zinc-300 hover:text-white light:text-theme-text-secondary hover:light:text-theme-text-primary" | |
| size={18} | |
| /> | |
| </button> | |
| )} | |
| </div> | |
| ) : ( | |
| <a | |
| href={ | |
| window.location.pathname === linkTo || ctrlPressed ? "#" : linkTo | |
| } | |
| className="w-full pl-2 py-1 overflow-hidden" | |
| aria-current={isActive ? "page" : ""} | |
| > | |
| <p | |
| className={`text-left text-sm truncate max-w-[150px] ${ | |
| isActive ? "font-medium text-white" : "text-theme-text-primary" | |
| }`} | |
| > | |
| {thread.name} | |
| </p> | |
| </a> | |
| )} | |
| {!!thread.slug && !thread.deleted && ( | |
| <div ref={optionsContainer} className="flex items-center"> | |
| {" "} | |
| {/* Added flex and items-center */} | |
| {ctrlPressed ? ( | |
| <button | |
| type="button" | |
| className="border-none" | |
| onClick={() => toggleMarkForDeletion(thread.id)} | |
| > | |
| <X | |
| className="text-zinc-300 light:text-theme-text-secondary hover:text-white hover:light:text-theme-text-primary" | |
| weight="bold" | |
| size={18} | |
| /> | |
| </button> | |
| ) : ( | |
| <div className="flex items-center w-fit group-hover:visible md:invisible gap-x-1"> | |
| <button | |
| type="button" | |
| className="border-none" | |
| onClick={() => setShowOptions(!showOptions)} | |
| aria-label="Thread options" | |
| > | |
| <DotsThree | |
| className="text-slate-300 light:text-theme-text-secondary hover:text-white hover:light:text-theme-text-primary" | |
| size={25} | |
| /> | |
| </button> | |
| </div> | |
| )} | |
| {showOptions && ( | |
| <OptionsMenu | |
| containerRef={optionsContainer} | |
| workspace={workspace} | |
| thread={thread} | |
| onRemove={onRemove} | |
| close={() => setShowOptions(false)} | |
| currentThreadSlug={threadSlug} | |
| /> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |
| function OptionsMenu({ | |
| containerRef, | |
| workspace, | |
| thread, | |
| onRemove, | |
| close, | |
| currentThreadSlug, | |
| }) { | |
| const menuRef = useRef(null); | |
| // Ref menu options | |
| const outsideClick = (e) => { | |
| if (!menuRef.current) return false; | |
| if ( | |
| !menuRef.current?.contains(e.target) && | |
| !containerRef.current?.contains(e.target) | |
| ) | |
| close(); | |
| return false; | |
| }; | |
| const isEsc = (e) => { | |
| if (e.key === "Escape" || e.key === "Esc") close(); | |
| }; | |
| function cleanupListeners() { | |
| window.removeEventListener("click", outsideClick); | |
| window.removeEventListener("keyup", isEsc); | |
| } | |
| // end Ref menu options | |
| useEffect(() => { | |
| function setListeners() { | |
| if (!menuRef?.current || !containerRef.current) return false; | |
| window.document.addEventListener("click", outsideClick); | |
| window.document.addEventListener("keyup", isEsc); | |
| } | |
| setListeners(); | |
| return cleanupListeners; | |
| }, [menuRef.current, containerRef.current]); | |
| const renameThread = async () => { | |
| const name = window | |
| .prompt("What would you like to rename this thread to?") | |
| ?.trim(); | |
| if (!name || name.length === 0) { | |
| close(); | |
| return; | |
| } | |
| const { message } = await Workspace.threads.update( | |
| workspace.slug, | |
| thread.slug, | |
| { name } | |
| ); | |
| if (!!message) { | |
| showToast(`Thread could not be updated! ${message}`, "error", { | |
| clear: true, | |
| }); | |
| close(); | |
| return; | |
| } | |
| thread.name = name; | |
| close(); | |
| }; | |
| const handleDelete = async () => { | |
| if ( | |
| !window.confirm( | |
| "Are you sure you want to delete this thread? All of its chats will be deleted. You cannot undo this." | |
| ) | |
| ) | |
| return; | |
| const success = await Workspace.threads.delete(workspace.slug, thread.slug); | |
| if (!success) { | |
| showToast("Thread could not be deleted!", "error", { clear: true }); | |
| return; | |
| } | |
| if (success) { | |
| showToast("Thread deleted successfully!", "success", { clear: true }); | |
| onRemove(thread.id); | |
| // Redirect if deleting the active thread | |
| if (currentThreadSlug === thread.slug) { | |
| window.location.href = paths.workspace.chat(workspace.slug); | |
| } | |
| return; | |
| } | |
| }; | |
| return ( | |
| <div | |
| ref={menuRef} | |
| className="absolute w-fit z-[20] top-[25px] right-[10px] bg-zinc-900 light:bg-theme-bg-sidebar light:border-[1px] light:border-theme-sidebar-border rounded-lg p-1" | |
| > | |
| <button | |
| onClick={renameThread} | |
| type="button" | |
| className="w-full rounded-md flex items-center p-2 gap-x-2 hover:bg-slate-500/20 text-slate-300 light:text-theme-text-primary" | |
| > | |
| <PencilSimple size={18} /> | |
| <p className="text-sm">Rename</p> | |
| </button> | |
| <button | |
| onClick={handleDelete} | |
| type="button" | |
| className="w-full rounded-md flex items-center p-2 gap-x-2 hover:bg-red-500/20 text-slate-300 light:text-theme-text-primary hover:text-red-100" | |
| > | |
| <Trash size={18} /> | |
| <p className="text-sm">Delete Thread</p> | |
| </button> | |
| </div> | |
| ); | |
| } | |