θΏ™ζ˜―indexlocζδΎ›ηš„ζœεŠ‘οΌŒδΈθ¦θΎ“ε…₯任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"onnxruntime-web": "^1.18.0",
"pluralize": "^8.0.0",
"react": "^18.2.0",
"react-beautiful-dnd": "13.1.1",
"react-device-detect": "^2.2.2",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default function ThreadItem({
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-2 rounded-bl-lg`}
} h-[50%] absolute top-0 left-3 rounded-bl-lg`}
></div>
{/* Downstroke border for next item */}
{hasNext && (
Expand All @@ -53,7 +53,7 @@ export default function ThreadItem({
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-2`}
} h-[100%] absolute top-0 left-3`}
></div>
)}

Expand Down
254 changes: 160 additions & 94 deletions frontend/src/components/Sidebar/ActiveWorkspaces/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,17 @@ import ManageWorkspace, {
} from "../../Modals/ManageWorkspace";
import paths from "@/utils/paths";
import { useParams } from "react-router-dom";
import { GearSix, SquaresFour, UploadSimple } from "@phosphor-icons/react";
import {
GearSix,
SquaresFour,
UploadSimple,
DotsSixVertical,
} from "@phosphor-icons/react";
import useUser from "@/hooks/useUser";
import ThreadContainer from "./ThreadContainer";
import { Link, useMatch } from "react-router-dom";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import showToast from "@/utils/toast";

export default function ActiveWorkspaces() {
const { slug } = useParams();
Expand All @@ -25,7 +32,7 @@ export default function ActiveWorkspaces() {
async function getWorkspaces() {
const workspaces = await Workspace.all();
setLoading(false);
setWorkspaces(workspaces);
setWorkspaces(Workspace.orderWorkspaces(workspaces));
}
getWorkspaces();
}, []);
Expand All @@ -44,108 +51,167 @@ export default function ActiveWorkspaces() {
);
}

/**
* Reorders workspaces in the UI via localstorage on client side.
* @param {number} startIndex - the index of the workspace to move
* @param {number} endIndex - the index to move the workspace to
*/
function reorderWorkspaces(startIndex, endIndex) {
const reorderedWorkspaces = Array.from(workspaces);
const [removed] = reorderedWorkspaces.splice(startIndex, 1);
reorderedWorkspaces.splice(endIndex, 0, removed);
setWorkspaces(reorderedWorkspaces);
const success = Workspace.storeWorkspaceOrder(
reorderedWorkspaces.map((w) => w.id)
);
if (!success) {
showToast("Failed to reorder workspaces", "error");
Workspace.all().then((workspaces) => setWorkspaces(workspaces));
}
}

const onDragEnd = (result) => {
if (!result.destination) return;
reorderWorkspaces(result.source.index, result.destination.index);
};

return (
<div role="list" aria-label="Workspaces" className="flex flex-col gap-y-2">
{workspaces.map((workspace) => {
const isActive = workspace.slug === slug;
return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="workspaces">
{(provided) => (
<div
className="flex flex-col w-full group"
key={workspace.id}
role="listitem"
role="list"
aria-label="Workspaces"
className="flex flex-col gap-y-2"
ref={provided.innerRef}
{...provided.droppableProps}
>
<div className="flex gap-x-2 items-center justify-between">
<a
href={isActive ? null : paths.workspace.chat(workspace.slug)}
aria-current={isActive ? "page" : ""}
className={`
transition-all duration-[200ms]
flex flex-grow w-[75%] gap-x-2 py-[6px] px-[12px] rounded-[4px] text-white justify-start items-center
bg-theme-sidebar-item-default
hover:bg-theme-sidebar-subitem-hover hover:font-bold
${isActive ? "bg-theme-sidebar-item-selected font-bold border-solid border-2 border-transparent light:border-blue-400" : ""}
`}
>
<div className="flex flex-row justify-between w-full">
<div className="flex items-center space-x-2 overflow-hidden">
<SquaresFour
weight={isActive ? "fill" : "regular"}
className="flex-shrink-0"
color={
isActive
? "var(--theme-sidebar-item-workspace-active)"
: "var(--theme-sidebar-item-workspace-inactive)"
}
size={24}
/>
<div className="w-[130px] overflow-hidden">
<p
className={`
text-[14px] leading-loose whitespace-nowrap overflow-hidden text-white
${isActive ? "font-bold" : "font-medium"} truncate
w-full group-hover:w-[100px] group-hover:font-bold group-hover:duration-200
`}
>
{workspace.name}
</p>
</div>
</div>
{user?.role !== "default" && (
{workspaces.map((workspace, index) => {
const isActive = workspace.slug === slug;
return (
<Draggable
key={workspace.id}
draggableId={workspace.id.toString()}
index={index}
isDragDisabled={user?.role === "default"}
>
{(provided, snapshot) => (
<div
className={`flex items-center gap-x-[2px] transition-opacity duration-200 ${isActive ? "opacity-100" : "opacity-0 group-hover:opacity-100"}`}
ref={provided.innerRef}
{...provided.draggableProps}
className={`flex flex-col w-full group ${
snapshot.isDragging ? "opacity-50" : ""
}`}
role="listitem"
>
<button
type="button"
onClick={(e) => {
e.preventDefault();
setSelectedWs(workspace);
showModal();
}}
className="border-none rounded-md flex items-center justify-center ml-auto p-[2px] hover:bg-[#646768] text-[#A7A8A9] hover:text-white"
>
<UploadSimple
className="h-[20px] w-[20px]"
weight="bold"
/>
</button>
<Link
to={
isInWorkspaceSettings
? paths.workspace.chat(workspace.slug)
: paths.workspace.settings.generalAppearance(
workspace.slug
)
}
className="rounded-md flex items-center justify-center text-[#A7A8A9] hover:text-white ml-auto p-[2px] hover:bg-[#646768]"
aria-label="General appearance settings"
>
<GearSix
color={
isInWorkspaceSettings && workspace.slug === slug
? "#46C8FF"
: undefined
<div className="flex gap-x-2 items-center justify-between">
<a
href={
isActive
? null
: paths.workspace.chat(workspace.slug)
}
weight="bold"
className="h-[20px] w-[20px]"
aria-current={isActive ? "page" : ""}
className={`
transition-all duration-[200ms]
flex flex-grow w-[75%] gap-x-2 py-[6px] pl-[4px] pr-[6px] rounded-[4px] text-white justify-start items-center
bg-theme-sidebar-item-default
hover:bg-theme-sidebar-subitem-hover hover:font-bold
${isActive ? "bg-theme-sidebar-item-selected font-bold light:outline-2 light:outline light:outline-blue-400 light:outline-offset-[-2px]" : ""}
`}
>
<div className="flex flex-row justify-between w-full items-center">
{user?.role !== "default" && (
<div
{...provided.dragHandleProps}
className="cursor-grab mr-[3px]"
>
<DotsSixVertical
size={20}
color="var(--theme-sidebar-item-workspace-active)"
weight="bold"
/>
</div>
)}
<div className="flex items-center space-x-2 overflow-hidden flex-grow">
<div className="w-[130px] overflow-hidden">
<p
className={`
text-[14px] leading-loose whitespace-nowrap overflow-hidden text-white
${isActive ? "font-bold" : "font-medium"} truncate
w-full group-hover:w-[100px] group-hover:font-bold group-hover:duration-200
`}
>
{workspace.name}
</p>
</div>
</div>
{user?.role !== "default" && (
<div
className={`flex items-center gap-x-[2px] transition-opacity duration-200 ${isActive ? "opacity-100" : "opacity-0 group-hover:opacity-100"}`}
>
<button
type="button"
onClick={(e) => {
e.preventDefault();
setSelectedWs(workspace);
showModal();
}}
className="border-none rounded-md flex items-center justify-center ml-auto p-[2px] hover:bg-[#646768] text-[#A7A8A9] hover:text-white"
>
<UploadSimple
className="h-[20px] w-[20px]"
// weight="bold"
/>
</button>
<Link
to={
isInWorkspaceSettings
? paths.workspace.chat(workspace.slug)
: paths.workspace.settings.generalAppearance(
workspace.slug
)
}
className="rounded-md flex items-center justify-center text-[#A7A8A9] hover:text-white ml-auto p-[2px] hover:bg-[#646768]"
aria-label="General appearance settings"
>
<GearSix
color={
isInWorkspaceSettings &&
workspace.slug === slug
? "#46C8FF"
: undefined
}
// weight="bold"
className="h-[20px] w-[20px]"
/>
</Link>
</div>
)}
</div>
</a>
</div>
{isActive && (
<ThreadContainer
workspace={workspace}
isActive={isActive}
/>
</Link>
)}
</div>
)}
</div>
</a>
</div>
{isActive && (
<ThreadContainer workspace={workspace} isActive={isActive} />
</Draggable>
);
})}
{provided.placeholder}
{showing && (
<ManageWorkspace
hideModal={hideModal}
providedSlug={selectedWs ? selectedWs.slug : null}
/>
)}
</div>
);
})}
{showing && (
<ManageWorkspace
hideModal={hideModal}
providedSlug={selectedWs ? selectedWs.slug : null}
/>
)}
</div>
)}
</Droppable>
</DragDropContext>
);
}
41 changes: 40 additions & 1 deletion frontend/src/models/workspace.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { API_BASE } from "@/utils/constants";
import { baseHeaders } from "@/utils/request";
import { baseHeaders, safeJsonParse } from "@/utils/request";
import { fetchEventSource } from "@microsoft/fetch-event-source";
import WorkspaceThread from "@/models/workspaceThread";
import { v4 } from "uuid";
import { ABORT_STREAM_EVENT } from "@/utils/chat";

const Workspace = {
workspaceOrderStorageKey: "anythingllm-workspace-order",

new: async function (data = {}) {
const { workspace, message } = await fetch(`${API_BASE}/workspace/new`, {
method: "POST",
Expand Down Expand Up @@ -469,6 +471,43 @@ const Workspace = {
);
return response.ok;
},

/**
* Reorders workspaces in the UI via localstorage on client side.
* @param {string[]} workspaceIds - array of workspace ids to reorder
* @returns {boolean}
*/
storeWorkspaceOrder: function (workspaceIds = []) {
try {
localStorage.setItem(
this.workspaceOrderStorageKey,
JSON.stringify(workspaceIds)
);
return true;
} catch (error) {
console.error("Error reordering workspaces:", error);
return false;
}
},

/**
* Orders workspaces based on the order preference stored in localstorage
* @param {Array} workspaces - array of workspace JSON objects
* @returns {Array} - ordered workspaces
*/
orderWorkspaces: function (workspaces = []) {
const workspaceOrderPreference =
safeJsonParse(localStorage.getItem(this.workspaceOrderStorageKey)) || [];
if (workspaceOrderPreference.length === 0) return workspaces;
const orderedWorkspaces = Array.from(workspaces);
orderedWorkspaces.sort(
(a, b) =>
workspaceOrderPreference.indexOf(a.id) -
workspaceOrderPreference.indexOf(b.id)
);
return orderedWorkspaces;
},

threads: WorkspaceThread,
};

Expand Down
Loading