mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2025-03-21 01:22:23 +00:00
Implement thread UI update (#722)
* Implement thread UI update * remove comment
This commit is contained in:
parent
ee3a79fdcf
commit
161dc5f901
3 changed files with 78 additions and 31 deletions
|
@ -7,13 +7,20 @@ import { useParams } from "react-router-dom";
|
||||||
import truncate from "truncate";
|
import truncate from "truncate";
|
||||||
|
|
||||||
const THREAD_CALLOUT_DETAIL_WIDTH = 26;
|
const THREAD_CALLOUT_DETAIL_WIDTH = 26;
|
||||||
export default function ThreadItem({ workspace, thread, onRemove, hasNext }) {
|
export default function ThreadItem({
|
||||||
|
idx,
|
||||||
|
activeIdx,
|
||||||
|
isActive,
|
||||||
|
workspace,
|
||||||
|
thread,
|
||||||
|
onRemove,
|
||||||
|
hasNext,
|
||||||
|
}) {
|
||||||
const optionsContainer = useRef(null);
|
const optionsContainer = useRef(null);
|
||||||
const { slug, threadSlug = null } = useParams();
|
const { slug } = useParams();
|
||||||
const [showOptions, setShowOptions] = useState(false);
|
const [showOptions, setShowOptions] = useState(false);
|
||||||
const [name, setName] = useState(thread.name);
|
const [name, setName] = useState(thread.name);
|
||||||
|
|
||||||
const isActive = threadSlug === thread.slug;
|
|
||||||
const linkTo = !thread.slug
|
const linkTo = !thread.slug
|
||||||
? paths.workspace.chat(slug)
|
? paths.workspace.chat(slug)
|
||||||
: paths.workspace.thread(slug, thread.slug);
|
: paths.workspace.thread(slug, thread.slug);
|
||||||
|
@ -23,33 +30,48 @@ export default function ThreadItem({ workspace, thread, onRemove, hasNext }) {
|
||||||
{/* Curved line Element and leader if required */}
|
{/* Curved line Element and leader if required */}
|
||||||
<div
|
<div
|
||||||
style={{ width: THREAD_CALLOUT_DETAIL_WIDTH / 2 }}
|
style={{ width: THREAD_CALLOUT_DETAIL_WIDTH / 2 }}
|
||||||
className="border-l border-b border-slate-300 h-[50%] absolute top-0 left-2 rounded-bl-lg"
|
className={`${
|
||||||
|
isActive
|
||||||
|
? "border-l-2 border-b-2 border-white"
|
||||||
|
: "border-l border-b border-slate-300"
|
||||||
|
} h-[50%] absolute top-0 left-2 rounded-bl-lg`}
|
||||||
></div>
|
></div>
|
||||||
|
{/* Downstroke border for next item */}
|
||||||
{hasNext && (
|
{hasNext && (
|
||||||
<div
|
<div
|
||||||
style={{ width: THREAD_CALLOUT_DETAIL_WIDTH / 2 }}
|
style={{ width: THREAD_CALLOUT_DETAIL_WIDTH / 2 }}
|
||||||
className="border-l border-slate-300 h-[100%] absolute top-0 left-2"
|
className={`${
|
||||||
|
idx <= activeIdx && !isActive
|
||||||
|
? "border-l-2 border-white"
|
||||||
|
: "border-l border-slate-300"
|
||||||
|
} h-[100%] absolute top-0 left-2`}
|
||||||
></div>
|
></div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Curved line inline placeholder for spacing */}
|
{/* Curved line inline placeholder for spacing - not visible */}
|
||||||
<div
|
<div
|
||||||
style={{ width: THREAD_CALLOUT_DETAIL_WIDTH }}
|
style={{ width: THREAD_CALLOUT_DETAIL_WIDTH }}
|
||||||
className="w-[26px] h-full"
|
className="w-[26px] h-full"
|
||||||
/>
|
/>
|
||||||
<div className="flex w-full items-center justify-between pr-2 group relative">
|
<div className="flex w-full items-center justify-between pr-2 group relative">
|
||||||
<a href={isActive ? "#" : linkTo} className="w-full">
|
{thread.deleted ? (
|
||||||
<p
|
<a className="w-full">
|
||||||
className={`text-left text-sm ${
|
<p className={`text-left text-sm text-slate-400/50 italic`}>
|
||||||
isActive
|
deleted thread
|
||||||
? "font-semibold text-slate-300"
|
</p>
|
||||||
: "text-slate-400 italic"
|
</a>
|
||||||
}`}
|
) : (
|
||||||
>
|
<a href={isActive ? "#" : linkTo} className="w-full">
|
||||||
{truncate(name, 25)}
|
<p
|
||||||
</p>
|
className={`text-left text-sm ${
|
||||||
</a>
|
isActive ? "font-bold text-white" : "text-slate-400"
|
||||||
{!!thread.slug && (
|
}`}
|
||||||
|
>
|
||||||
|
{truncate(name, 25)}
|
||||||
|
</p>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{!!thread.slug && !thread.deleted && (
|
||||||
<div ref={optionsContainer}>
|
<div ref={optionsContainer}>
|
||||||
<div className="flex items-center w-fit group-hover:visible md:invisible gap-x-1">
|
<div className="flex items-center w-fit group-hover:visible md:invisible gap-x-1">
|
||||||
<button
|
<button
|
||||||
|
|
|
@ -4,8 +4,10 @@ import showToast from "@/utils/toast";
|
||||||
import { Plus, CircleNotch } from "@phosphor-icons/react";
|
import { Plus, CircleNotch } from "@phosphor-icons/react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import ThreadItem from "./ThreadItem";
|
import ThreadItem from "./ThreadItem";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
|
||||||
export default function ThreadContainer({ workspace }) {
|
export default function ThreadContainer({ workspace }) {
|
||||||
|
const { threadSlug = null } = useParams();
|
||||||
const [threads, setThreads] = useState([]);
|
const [threads, setThreads] = useState([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
@ -20,7 +22,12 @@ export default function ThreadContainer({ workspace }) {
|
||||||
}, [workspace.slug]);
|
}, [workspace.slug]);
|
||||||
|
|
||||||
function removeThread(threadId) {
|
function removeThread(threadId) {
|
||||||
setThreads((prev) => prev.filter((thread) => thread.id !== threadId));
|
setThreads((prev) =>
|
||||||
|
prev.map((_t) => {
|
||||||
|
if (_t.id !== threadId) return _t;
|
||||||
|
return { ..._t, deleted: true };
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
|
@ -33,15 +40,26 @@ export default function ThreadContainer({ workspace }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const activeThreadIdx = !!threads.find(
|
||||||
|
(thread) => thread?.slug === threadSlug
|
||||||
|
)
|
||||||
|
? threads.findIndex((thread) => thread?.slug === threadSlug) + 1
|
||||||
|
: 0;
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<ThreadItem
|
<ThreadItem
|
||||||
|
idx={0}
|
||||||
|
activeIdx={activeThreadIdx}
|
||||||
|
isActive={activeThreadIdx === 0}
|
||||||
thread={{ slug: null, name: "default" }}
|
thread={{ slug: null, name: "default" }}
|
||||||
hasNext={threads.length > 0}
|
hasNext={threads.length > 0}
|
||||||
/>
|
/>
|
||||||
{threads.map((thread, i) => (
|
{threads.map((thread, i) => (
|
||||||
<ThreadItem
|
<ThreadItem
|
||||||
key={thread.slug}
|
key={thread.slug}
|
||||||
|
idx={i + 1}
|
||||||
|
activeIdx={activeThreadIdx}
|
||||||
|
isActive={activeThreadIdx === i + 1}
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
onRemove={removeThread}
|
onRemove={removeThread}
|
||||||
thread={thread}
|
thread={thread}
|
||||||
|
@ -54,7 +72,7 @@ export default function ThreadContainer({ workspace }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function NewThreadButton({ workspace }) {
|
function NewThreadButton({ workspace }) {
|
||||||
const [loading, setLoading] = useState();
|
const [loading, setLoading] = useState(false);
|
||||||
const onClick = async () => {
|
const onClick = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const { thread, error } = await Workspace.threads.new(workspace.slug);
|
const { thread, error } = await Workspace.threads.new(workspace.slug);
|
||||||
|
@ -74,15 +92,22 @@ function NewThreadButton({ workspace }) {
|
||||||
className="w-full relative flex h-[40px] items-center border-none hover:bg-slate-600/20 rounded-lg"
|
className="w-full relative flex h-[40px] items-center border-none hover:bg-slate-600/20 rounded-lg"
|
||||||
>
|
>
|
||||||
<div className="flex w-full gap-x-2 items-center pl-4">
|
<div className="flex w-full gap-x-2 items-center pl-4">
|
||||||
|
<div className="bg-zinc-600 p-2 rounded-lg h-[24px] w-[24px] flex items-center justify-center">
|
||||||
|
{loading ? (
|
||||||
|
<CircleNotch
|
||||||
|
weight="bold"
|
||||||
|
size={14}
|
||||||
|
className="shrink-0 animate-spin text-slate-100"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Plus weight="bold" size={14} className="shrink-0 text-slate-100" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<CircleNotch className="animate-spin text-slate-300" />
|
<p className="text-left text-slate-100 text-sm">Starting Thread...</p>
|
||||||
) : (
|
) : (
|
||||||
<Plus className="text-slate-300" />
|
<p className="text-left text-slate-100 text-sm">New Thread</p>
|
||||||
)}
|
|
||||||
{loading ? (
|
|
||||||
<p className="text-left text-slate-300 text-sm">starting thread...</p>
|
|
||||||
) : (
|
|
||||||
<p className="text-left text-slate-300 text-sm">new thread</p>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -80,12 +80,12 @@ export default function ActiveWorkspaces() {
|
||||||
href={isActive ? null : paths.workspace.chat(workspace.slug)}
|
href={isActive ? null : paths.workspace.chat(workspace.slug)}
|
||||||
className={`
|
className={`
|
||||||
transition-all duration-[200ms]
|
transition-all duration-[200ms]
|
||||||
flex flex-grow w-[75%] gap-x-2 py-[6px] px-[12px] rounded-lg text-slate-200 justify-start items-center border
|
flex flex-grow w-[75%] gap-x-2 py-[6px] px-[12px] rounded-lg text-slate-200 justify-start items-center
|
||||||
hover:bg-workspace-item-selected-gradient hover:border-slate-100 hover:border-opacity-50
|
hover:bg-workspace-item-selected-gradient
|
||||||
${
|
${
|
||||||
isActive
|
isActive
|
||||||
? "bg-workspace-item-selected-gradient border-slate-100 border-opacity-50"
|
? "border-2 bg-workspace-item-selected-gradient border-white"
|
||||||
: "bg-workspace-item-gradient bg-opacity-60 border-transparent"
|
: "border bg-workspace-item-gradient bg-opacity-60 border-transparent hover:border-slate-100 hover:border-opacity-50"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row justify-between w-full">
|
<div className="flex flex-row justify-between w-full">
|
||||||
|
|
Loading…
Add table
Reference in a new issue