merlyn/frontend/src/components/WorkspaceChat/ChatContainer/PromptInput/AttachItem/index.jsx
Sean Hatfield d325b07182
Implement new home page redesign (#4931)
* remove legacy home page components, update home page to new layout

* update PromptInput component styles to match new designs, make quick action buttons functional

* home page chat creates new thread in last used workspace

* fix slash commands and agent popup on home page

* disable llm workspace selector action in home page

* add drag and drop file support to home page

* fix behavior of drag and drop on home page

* handle pasting attachments in home page

* update empty state of workspace chat to use new ui

* update empty workspace ui to match home page design, fix flickering loading states

* convert quick action buttons to component, add to empty state ws chat

* fix hover state light mode in quick actions

* add suggested messages subcomponent to empty ws/thread

* adjust width, rounded edges of prompt input

* only show quick actions for admin/manager role

* fix hover states for quick actions and suggested messages component

* make upload document quick action trigger parsed document upload

* fix mic behavior in homepage, ws chat, ws thread chat

* fix margin between prompt input and quick actions

* Simplify message presets by removing heading input (#4915)

* Remove heading input from message presets, merge legacy headings on edit

* filter out empty messages from state after saving

* mark form as dirty on input change

* styling

---------

Co-authored-by: Timothy Carambat <rambat1010@gmail.com>

* convert SuggestedMessages to component, render SuggestedMessages in home page to target ws

* fix broken handleMessageChange reference

* add translations for QuickActions

* lint

* fix home page chat submission broken by PromptInput onChange removal

* fix prompt input remount race condition, home page suggested message flicker

* remove unused handleSendSuggestedMessage from ChatHistory

* add greeting text to main-page translations, remove defaults

* fix file deletion in parsed files menu on home page

* add virtual thread sidebar state and workspace indicator on home page

* show workspace llm selector on home page when workspace exists

* show home page for all user roles with rbac quick actions

* fix positioning of agent and slash command popups

* remove workspace indicator from home page, match empty state spacing

* Normalize translations for home page redesign (#4986)

* normalize translations

* update translations with DMR

* accidentally changed es translation

* normalize translations for main-page.greeting

* update translations with DMR

---------

Co-authored-by: Timothy Carambat <rambat1010@gmail.com>

* update translations

* create new workspace in native language
Cleanup workspace page from empty state handling

* update quick action show logic

* fix send button

---------

Co-authored-by: Timothy Carambat <rambat1010@gmail.com>
2026-02-19 12:40:36 -08:00

143 lines
4.8 KiB
JavaScript

import { PaperclipHorizontal } from "@phosphor-icons/react";
import { Tooltip } from "react-tooltip";
import { useTranslation } from "react-i18next";
import { useRef, useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import Workspace from "@/models/workspace";
import {
ATTACHMENTS_PROCESSED_EVENT,
REMOVE_ATTACHMENT_EVENT,
} from "../../DnDWrapper";
import { useTheme } from "@/hooks/useTheme";
import ParsedFilesMenu from "./ParsedFilesMenu";
/**
* This is a simple proxy component that clicks on the DnD file uploader for the user.
* @returns
*/
export default function AttachItem({
workspaceSlug = null,
workspaceThreadSlug = null,
}) {
const { t } = useTranslation();
const { theme } = useTheme();
const params = useParams();
const slug = workspaceSlug || params.slug;
const threadSlug = workspaceThreadSlug ?? params.threadSlug ?? null;
const tooltipRef = useRef(null);
const [isEmbedding, setIsEmbedding] = useState(false);
const [files, setFiles] = useState([]);
const [currentTokens, setCurrentTokens] = useState(0);
const [contextWindow, setContextWindow] = useState(Infinity);
const [showTooltip, setShowTooltip] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const fetchFiles = () => {
if (!slug) return;
if (isEmbedding) return;
setIsLoading(true);
Workspace.getParsedFiles(slug, threadSlug)
.then(({ files, contextWindow, currentContextTokenCount }) => {
setFiles(files);
setShowTooltip(files.length > 0);
setContextWindow(contextWindow);
setCurrentTokens(currentContextTokenCount);
})
.finally(() => {
setIsLoading(false);
});
};
/**
* Handles the removal of an attachment from the parsed files
* and triggers a re-fetch of the parsed files.
* This function handles when the user clicks the X on an Attachment via the AttachmentManager
* so we need to sync the state in the ParsedFilesMenu picker here.
*/
async function handleRemoveAttachment(e) {
const { document } = e.detail;
await Workspace.deleteParsedFiles(slug, [document.id]);
fetchFiles();
}
/**
* Handles the click event for the attach item button.
* @param {MouseEvent} e - The click event.
* @returns {void}
*/
function handleClick(e) {
e?.target?.blur();
document?.getElementById("dnd-chat-file-uploader")?.click();
return;
}
useEffect(() => {
fetchFiles();
window.addEventListener(ATTACHMENTS_PROCESSED_EVENT, fetchFiles);
window.addEventListener(REMOVE_ATTACHMENT_EVENT, handleRemoveAttachment);
return () => {
window.removeEventListener(ATTACHMENTS_PROCESSED_EVENT, fetchFiles);
window.removeEventListener(
REMOVE_ATTACHMENT_EVENT,
handleRemoveAttachment
);
};
}, [slug, threadSlug]);
return (
<>
<button
id="attach-item-btn"
data-tooltip-id="tooltip-attach-item-btn"
aria-label={t("chat_window.attach_file")}
type="button"
onClick={handleClick}
onPointerEnter={fetchFiles}
className={`border-none relative flex justify-center items-center opacity-60 hover:opacity-100 light:opacity-100 light:hover:opacity-60 cursor-pointer`}
>
<div className="relative">
<PaperclipHorizontal
color="var(--theme-sidebar-footer-icon-fill)"
className="w-[20px] h-[20px] pointer-events-none text-white rotate-90 -scale-y-100"
/>
{files.length > 0 && (
<div className="absolute -top-2 right-[1%] bg-white text-black light:invert text-[8px] rounded-full px-1 flex items-center justify-center">
{files.length}
</div>
)}
</div>
</button>
{showTooltip && (
<Tooltip
ref={tooltipRef}
id="tooltip-attach-item-btn"
place="top"
opacity={1}
clickable={!isEmbedding}
delayShow={300}
delayHide={isEmbedding ? 999999 : 800} // Prevent tooltip from hiding during embedding
arrowColor={
theme === "light"
? "var(--theme-modal-border)"
: "var(--theme-bg-primary)"
}
className="z-99 !w-[400px] !bg-theme-bg-primary !px-[5px] !rounded-lg !pointer-events-auto light:border-2 light:border-theme-modal-border"
>
<ParsedFilesMenu
onEmbeddingChange={setIsEmbedding}
tooltipRef={tooltipRef}
isLoading={isLoading}
files={files}
setFiles={setFiles}
currentTokens={currentTokens}
setCurrentTokens={setCurrentTokens}
contextWindow={contextWindow}
workspaceSlug={slug}
threadSlug={threadSlug}
/>
</Tooltip>
)}
</>
);
}