Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f4adea7
remove legacy home page components, update home page to new layout
shatfield4 Jan 29, 2026
419d0d1
update PromptInput component styles to match new designs, make quick …
shatfield4 Jan 31, 2026
b85b2e1
home page chat creates new thread in last used workspace
shatfield4 Feb 3, 2026
f8c7cde
fix slash commands and agent popup on home page
shatfield4 Feb 3, 2026
d8a47c6
disable llm workspace selector action in home page
shatfield4 Feb 3, 2026
2bde5ce
add drag and drop file support to home page
shatfield4 Feb 3, 2026
d2f7d39
fix behavior of drag and drop on home page
shatfield4 Feb 3, 2026
1910088
handle pasting attachments in home page
shatfield4 Feb 3, 2026
5f6c87b
update empty state of workspace chat to use new ui
shatfield4 Feb 4, 2026
8f0c20d
update empty workspace ui to match home page design, fix flickering l…
shatfield4 Feb 4, 2026
f4f6319
Merge branch 'master' into 4911-feat-home-page-redesign-implementation
shatfield4 Feb 4, 2026
49b4b74
convert quick action buttons to component, add to empty state ws chat
shatfield4 Feb 4, 2026
e9f6f6e
fix hover state light mode in quick actions
shatfield4 Feb 4, 2026
b9a8fca
add suggested messages subcomponent to empty ws/thread
shatfield4 Feb 4, 2026
b74de61
adjust width, rounded edges of prompt input
shatfield4 Feb 4, 2026
908eccc
only show quick actions for admin/manager role
shatfield4 Feb 4, 2026
b3f7ac0
fix hover states for quick actions and suggested messages component
shatfield4 Feb 5, 2026
d95cf56
make upload document quick action trigger parsed document upload
shatfield4 Feb 5, 2026
383ef0e
fix mic behavior in homepage, ws chat, ws thread chat
shatfield4 Feb 5, 2026
d7d2681
fix margin between prompt input and quick actions
shatfield4 Feb 5, 2026
197f351
Merge branch 'master' of github.com:Mintplex-Labs/anything-llm into 4…
shatfield4 Feb 5, 2026
399613b
Simplify message presets by removing heading input (#4915)
shatfield4 Feb 9, 2026
63c5592
convert SuggestedMessages to component, render SuggestedMessages in h…
shatfield4 Feb 9, 2026
7e4f1f9
Merge branch 'master' of github.com:Mintplex-Labs/anything-llm into 4…
shatfield4 Feb 9, 2026
7ebc6cf
fix broken handleMessageChange reference
shatfield4 Feb 9, 2026
35bdf6e
add translations for QuickActions
shatfield4 Feb 10, 2026
3513fdf
lint
shatfield4 Feb 10, 2026
87e1180
fix home page chat submission broken by PromptInput onChange removal
shatfield4 Feb 10, 2026
4771699
fix prompt input remount race condition, home page suggested message …
shatfield4 Feb 11, 2026
cf47ce6
remove unused handleSendSuggestedMessage from ChatHistory
shatfield4 Feb 11, 2026
b0f27e1
Merge branch 'master' into 4911-feat-home-page-redesign-implementation
timothycarambat Feb 12, 2026
060f489
add greeting text to main-page translations, remove defaults
shatfield4 Feb 13, 2026
86625c7
fix file deletion in parsed files menu on home page
shatfield4 Feb 13, 2026
d30eb4d
add virtual thread sidebar state and workspace indicator on home page
shatfield4 Feb 13, 2026
066da16
show workspace llm selector on home page when workspace exists
shatfield4 Feb 13, 2026
5a5ff67
show home page for all user roles with rbac quick actions
shatfield4 Feb 14, 2026
d8f7c65
fix positioning of agent and slash command popups
shatfield4 Feb 16, 2026
525cdd0
remove workspace indicator from home page, match empty state spacing
shatfield4 Feb 17, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,15 @@ export default function ThreadItem({
hasNext,
ctrlPressed = false,
}) {
const { slug, threadSlug = null } = useParams();
const { slug: urlSlug, threadSlug = null } = useParams();
const workspaceSlug = workspace?.slug ?? urlSlug;
const optionsContainer = useRef(null);
const [showOptions, setShowOptions] = useState(false);
const linkTo = !thread.slug
? paths.workspace.chat(slug)
: paths.workspace.thread(slug, thread.slug);
const linkTo = thread.virtual
? "/"
: !thread.slug
? paths.workspace.chat(workspaceSlug)
: paths.workspace.thread(workspaceSlug, thread.slug);

const { ref } = useScrollActiveItemIntoView({
isActive,
Expand Down Expand Up @@ -112,7 +115,7 @@ export default function ThreadItem({
</p>
</a>
)}
{!!thread.slug && !thread.deleted && (
{!!thread.slug && !thread.deleted && !thread.virtual && (
<div ref={optionsContainer} className="flex items-center">
{" "}
{/* Added flex and items-center */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ import ThreadItem from "./ThreadItem";
import { useParams } from "react-router-dom";
export const THREAD_RENAME_EVENT = "renameThread";

export default function ThreadContainer({ workspace }) {
export default function ThreadContainer({
workspace,
isVirtualThread = false,
}) {
const { threadSlug = null } = useParams();
const [threads, setThreads] = useState([]);
const [loading, setLoading] = useState(true);
Expand Down Expand Up @@ -109,6 +112,12 @@ export default function ThreadContainer({ workspace }) {
}, 500);
}

function getActiveThreadIdx() {
if (isVirtualThread) return threads.length + 1;
const idx = threads.findIndex((t) => t?.slug === threadSlug);
return idx >= 0 ? idx + 1 : 0;
}

if (loading) {
return (
<div className="flex flex-col bg-pulse w-full h-10 items-center justify-center">
Expand All @@ -117,20 +126,17 @@ export default function ThreadContainer({ workspace }) {
);
}

const activeThreadIdx = !!threads.find(
(thread) => thread?.slug === threadSlug
)
? threads.findIndex((thread) => thread?.slug === threadSlug) + 1
: 0;
const activeThreadIdx = getActiveThreadIdx();

return (
<div className="flex flex-col" role="list" aria-label="Threads">
<ThreadItem
idx={0}
activeIdx={activeThreadIdx}
isActive={activeThreadIdx === 0}
workspace={workspace}
thread={{ slug: null, name: "default" }}
hasNext={threads.length > 0}
hasNext={threads.length > 0 || isVirtualThread}
/>
{threads.map((thread, i) => (
<ThreadItem
Expand All @@ -143,9 +149,19 @@ export default function ThreadContainer({ workspace }) {
workspace={workspace}
onRemove={removeThread}
thread={thread}
hasNext={i !== threads.length - 1}
hasNext={i !== threads.length - 1 || isVirtualThread}
/>
))}
{isVirtualThread && (
<ThreadItem
idx={activeThreadIdx}
activeIdx={activeThreadIdx}
isActive={true}
workspace={workspace}
thread={{ slug: null, name: "*New Thread", virtual: true }}
hasNext={false}
/>
)}
<DeleteAllThreadButton
ctrlPressed={ctrlPressed}
threads={threads}
Expand Down
24 changes: 21 additions & 3 deletions frontend/src/components/Sidebar/ActiveWorkspaces/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import ManageWorkspace, {
useManageWorkspaceModal,
} from "../../Modals/ManageWorkspace";
import paths from "@/utils/paths";
import { useParams, useNavigate } from "react-router-dom";
import { useParams, useNavigate, useMatch } from "react-router-dom";
import { GearSix, UploadSimple, DotsSixVertical } from "@phosphor-icons/react";
import useUser from "@/hooks/useUser";
import ThreadContainer from "./ThreadContainer";
import { useMatch } from "react-router-dom";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import showToast from "@/utils/toast";
import { LAST_VISITED_WORKSPACE } from "@/utils/constants";
import { safeJsonParse } from "@/utils/request";

export default function ActiveWorkspaces() {
const navigate = useNavigate();
Expand All @@ -23,6 +24,7 @@ export default function ActiveWorkspaces() {
const { showing, showModal, hideModal } = useManageWorkspaceModal();
const { user } = useUser();
const isInWorkspaceSettings = !!useMatch("/workspace/:slug/settings/:tab");
const isHomePage = !!useMatch("/");

useEffect(() => {
async function getWorkspaces() {
Expand Down Expand Up @@ -71,6 +73,20 @@ export default function ActiveWorkspaces() {
reorderWorkspaces(result.source.index, result.destination.index);
};

// When on the home page, resolve which workspace should be virtually active
const virtualActiveSlug = (() => {
if (!isHomePage || workspaces.length === 0) return null;
const lastVisited = safeJsonParse(
localStorage.getItem(LAST_VISITED_WORKSPACE)
);
if (
lastVisited?.slug &&
workspaces.some((ws) => ws.slug === lastVisited.slug)
)
return lastVisited.slug;
return workspaces[0]?.slug ?? null;
})();

return (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="workspaces">
Expand All @@ -83,7 +99,8 @@ export default function ActiveWorkspaces() {
{...provided.droppableProps}
>
{workspaces.map((workspace, index) => {
const isActive = workspace.slug === slug;
const isVirtuallyActive = workspace.slug === virtualActiveSlug;
const isActive = workspace.slug === slug || isVirtuallyActive;
return (
<Draggable
key={workspace.id}
Expand Down Expand Up @@ -190,6 +207,7 @@ export default function ActiveWorkspaces() {
<ThreadContainer
workspace={workspace}
isActive={isActive}
isVirtualThread={isVirtuallyActive}
/>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@ import { useManageWorkspaceModal } from "../../../Modals/ManageWorkspace";
import ManageWorkspace from "../../../Modals/ManageWorkspace";
import { ArrowDown } from "@phosphor-icons/react";
import debounce from "lodash.debounce";
import useUser from "@/hooks/useUser";
import Chartable from "./Chartable";
import Workspace from "@/models/workspace";
import { useParams } from "react-router-dom";
import paths from "@/utils/paths";
import Appearance from "@/models/appearance";
import useTextSize from "@/hooks/useTextSize";
import useChatHistoryScrollHandle from "@/hooks/useChatHistoryScrollHandle";
import { useTranslation } from "react-i18next";
import { useChatMessageAlignment } from "@/hooks/useChatMessageAlignment";
import { ThoughtExpansionProvider } from "./ThoughtContainer";

Expand All @@ -32,16 +30,13 @@ export default forwardRef(function (
sendCommand,
updateHistory,
regenerateAssistantMessage,
hasAttachments = false,
},
ref
) {
const { t } = useTranslation();
const lastScrollTopRef = useRef(0);
const chatHistoryRef = useRef(null);
const { user } = useUser();
const { threadSlug = null } = useParams();
const { showing, showModal, hideModal } = useManageWorkspaceModal();
const { showing, hideModal } = useManageWorkspaceModal();
const [isAtBottom, setIsAtBottom] = useState(true);
const [isUserScrolling, setIsUserScrolling] = useState(false);
const isStreaming = history[history.length - 1]?.animate;
Expand Down Expand Up @@ -98,10 +93,6 @@ export default forwardRef(function (
scrollToBottom,
});

const handleSendSuggestedMessage = (heading, message) => {
sendCommand({ text: `${heading} ${message}`, autoSubmit: true });
};

const saveEditedMessage = async ({
editedMessage,
chatId,
Expand Down Expand Up @@ -197,46 +188,6 @@ export default forwardRef(function (
[compiledHistory.length, lastMessageInfo]
);

if (history.length === 0 && !hasAttachments) {
return (
<div className="flex flex-col h-full md:mt-0 pb-44 md:pb-40 w-full justify-end items-center">
<div className="flex flex-col items-center md:items-start md:max-w-[600px] w-full px-4">
<p className="text-white/60 text-lg font-base py-4">
{t("chat_window.welcome")}
</p>
{!user || user.role !== "default" ? (
<p className="w-full items-center text-white/60 text-lg font-base flex flex-col md:flex-row gap-x-1">
{t("chat_window.get_started")}
<span
className="underline font-medium cursor-pointer"
onClick={showModal}
>
{t("chat_window.upload")}
</span>
{t("chat_window.or")}{" "}
<b className="font-medium italic">{t("chat_window.send_chat")}</b>
</p>
) : (
<p className="w-full items-center text-white/60 text-lg font-base flex flex-col md:flex-row gap-x-1">
{t("chat_window.get_started_default")}{" "}
<b className="font-medium italic">{t("chat_window.send_chat")}</b>
</p>
)}
<WorkspaceChatSuggestions
suggestions={workspace?.suggestedMessages ?? []}
sendSuggestion={handleSendSuggestedMessage}
/>
</div>
{showing && (
<ManageWorkspace
hideModal={hideModal}
providedSlug={workspace.slug}
/>
)}
</div>
);
}

return (
<ThoughtExpansionProvider>
<div
Expand Down Expand Up @@ -282,24 +233,6 @@ const getLastMessageInfo = (history) => {
};
};

function WorkspaceChatSuggestions({ suggestions = [], sendSuggestion }) {
if (suggestions.length === 0) return null;
return (
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 text-theme-text-primary text-xs mt-10 w-full justify-center">
{suggestions.map((suggestion, index) => (
<button
key={index}
className="text-left p-2.5 rounded-xl bg-theme-sidebar-footer-icon hover:bg-theme-sidebar-footer-icon-hover border border-theme-border"
onClick={() => sendSuggestion(suggestion.heading, suggestion.message)}
>
<p className="font-semibold">{suggestion.heading}</p>
<p>{suggestion.message}</p>
</button>
))}
</div>
);
}

/**
* Builds the history of messages for the chat.
* This is mostly useful for rendering the history in a way that is easy to understand.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ export default function AvailableAgentsButton({ showing, setShowAgents }) {
data-tooltip-content={t("chat_window.agents")}
aria-label={t("chat_window.agents")}
onClick={() => setShowAgents(!showing)}
className={`flex justify-center items-center cursor-pointer ${
className={`flex justify-center items-center cursor-pointer opacity-60 hover:opacity-100 light:opacity-100 light:hover:opacity-60 ${
showing ? "!opacity-100" : ""
}`}
>
<At
color="var(--theme-sidebar-footer-icon-fill)"
className={`w-[22px] h-[22px] pointer-events-none text-theme-text-primary opacity-60 hover:opacity-100 light:opacity-100 light:hover:opacity-60`}
className="w-[20px] h-[20px] pointer-events-none text-theme-text-primary"
/>
<Tooltip
id="tooltip-agent-list-btn"
Expand All @@ -47,6 +47,7 @@ export function AvailableAgents({
setShowing,
sendCommand,
promptRef,
centered = false,
}) {
const formRef = useRef(null);
const agentSessionActive = useIsAgentSessionActive();
Expand Down Expand Up @@ -88,10 +89,16 @@ export function AvailableAgents({
return (
<>
<div hidden={!showing}>
<div className="w-full flex justify-center absolute bottom-[130px] md:bottom-[150px] left-0 z-10 px-4">
<div
className={
centered
? "w-full flex justify-center md:justify-start absolute top-full mt-2 left-0 z-10 px-4 md:px-0 md:pl-[57px]"
: "flex justify-center md:justify-start absolute bottom-[130px] md:bottom-[150px] left-0 right-0 z-10 max-w-[750px] mx-auto px-4 md:px-0 md:pl-[57px]"
}
>
<div
ref={formRef}
className="w-[600px] p-2 bg-theme-action-menu-bg rounded-2xl shadow flex-col justify-center items-start gap-2.5 inline-flex"
className="w-[600px] p-2 bg-theme-action-menu-bg rounded-2xl shadow flex-col justify-center items-start gap-2.5 inline-flex overflow-y-auto max-h-[200px] no-scroll"
>
<button
onClick={handleAgentClick}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useState } from "react";
import { X, CircleNotch, Warning } from "@phosphor-icons/react";
import Workspace from "@/models/workspace";
import { useParams } from "react-router-dom";
import { nFormatter } from "@/utils/numbers";
import showToast from "@/utils/toast";
import pluralize from "pluralize";
Expand All @@ -17,13 +16,14 @@ export default function ParsedFilesMenu({
setCurrentTokens,
contextWindow,
isLoading,
workspaceSlug,
threadSlug = null,
}) {
const { user } = useUser();
const canEmbed = !user || user.role !== "default";
const initialContextWindowLimitExceeded =
contextWindow &&
currentTokens >= contextWindow * Workspace.maxContextWindowLimit;
const { slug, threadSlug = null } = useParams();
const [isEmbedding, setIsEmbedding] = useState(false);
const [embedProgress, setEmbedProgress] = useState(1);
const [contextWindowLimitExceeded, setContextWindowLimitExceeded] = useState(
Expand All @@ -35,7 +35,7 @@ export default function ParsedFilesMenu({
e.stopPropagation();
if (!file?.id) return;

const success = await Workspace.deleteParsedFiles(slug, [file.id]);
const success = await Workspace.deleteParsedFiles(workspaceSlug, [file.id]);
if (!success) return;

// Update the local files list and current tokens
Expand All @@ -48,7 +48,7 @@ export default function ParsedFilesMenu({
})
);
const { currentContextTokenCount } = await Workspace.getParsedFiles(
slug,
workspaceSlug,
threadSlug
);
const newContextWindowLimitExceeded =
Expand All @@ -73,15 +73,15 @@ export default function ParsedFilesMenu({
let completed = 0;
await Promise.all(
files.map((file) =>
Workspace.embedParsedFile(slug, file.id).then(() => {
Workspace.embedParsedFile(workspaceSlug, file.id).then(() => {
completed++;
setEmbedProgress(completed + 1);
})
)
);
setFiles([]);
const { currentContextTokenCount } = await Workspace.getParsedFiles(
slug,
workspaceSlug,
threadSlug
);
setCurrentTokens(currentContextTokenCount);
Expand Down
Loading
Loading