Feat: enhance sending files UI

This commit is contained in:
igorrCarvalho 2024-05-30 16:38:35 -03:00
commit e56ce2f2b4
5 changed files with 130 additions and 104 deletions

View file

@ -1,5 +1,5 @@
export const getClassNamesFilePreview = (inputFocus) => { export const getClassNamesFilePreview = (inputFocus) => {
return `flex w-full items-center gap-4 rounded-t-lg bg-background px-14 py-5 overflow-auto custom-scroll ${ return `transition-all duration-300 flex w-full items-center gap-4 rounded-t-lg bg-background px-14 py-5 overflow-auto custom-scroll ${
inputFocus inputFocus
? "border-2 border-b-0 border-ring" ? "border-2 border-b-0 border-ring"
: "border border-b-0 border-border" : "border border-b-0 border-border"

View file

@ -7,7 +7,9 @@ import remarkMath from "remark-math";
import MaleTechnology from "../../../../../assets/male-technologist.png"; import MaleTechnology from "../../../../../assets/male-technologist.png";
import Robot from "../../../../../assets/robot.png"; import Robot from "../../../../../assets/robot.png";
import CodeTabsComponent from "../../../../../components/codeTabsComponent"; import CodeTabsComponent from "../../../../../components/codeTabsComponent";
import IconComponent from "../../../../../components/genericIconComponent"; import IconComponent, {
ForwardedIconComponent,
} from "../../../../../components/genericIconComponent";
import SanitizedHTMLWrapper from "../../../../../components/sanitizedHTMLWrapper"; import SanitizedHTMLWrapper from "../../../../../components/sanitizedHTMLWrapper";
import useAlertStore from "../../../../../stores/alertStore"; import useAlertStore from "../../../../../stores/alertStore";
import useFlowStore from "../../../../../stores/flowStore"; import useFlowStore from "../../../../../stores/flowStore";
@ -22,6 +24,7 @@ export default function ChatMessage({
updateChat, updateChat,
setLockChat, setLockChat,
}: chatMessagePropsType): JSX.Element { }: chatMessagePropsType): JSX.Element {
const [showFile, setShowFile] = useState<boolean>(true);
const convert = new Convert({ newline: true }); const convert = new Convert({ newline: true });
const [hidden, setHidden] = useState(true); const [hidden, setHidden] = useState(true);
const template = chat.template; const template = chat.template;
@ -235,7 +238,7 @@ dark:prose-invert"
}, },
]} ]}
activeTab={"0"} activeTab={"0"}
setActiveTab={() => { }} setActiveTab={() => {}}
/> />
) : ( ) : (
<code className={className} {...props}> <code className={className} {...props}>
@ -277,65 +280,77 @@ dark:prose-invert"
<span className="prose text-primary word-break-break-word dark:prose-invert"> <span className="prose text-primary word-break-break-word dark:prose-invert">
{promptOpen {promptOpen
? template?.split("\n")?.map((line, index) => { ? template?.split("\n")?.map((line, index) => {
const regex = /{([^}]+)}/g; const regex = /{([^}]+)}/g;
let match; let match;
let parts: Array<JSX.Element | string> = []; let parts: Array<JSX.Element | string> = [];
let lastIndex = 0; let lastIndex = 0;
while ((match = regex.exec(line)) !== null) { while ((match = regex.exec(line)) !== null) {
// Push text up to the match // Push text up to the match
if (match.index !== lastIndex) { if (match.index !== lastIndex) {
parts.push(line.substring(lastIndex, match.index)); parts.push(line.substring(lastIndex, match.index));
} }
// Push div with matched text // Push div with matched text
if (chat.message[match[1]]) { if (chat.message[match[1]]) {
parts.push( parts.push(
<span className="chat-message-highlight"> <span className="chat-message-highlight">
{chat.message[match[1]]} {chat.message[match[1]]}
</span> </span>,
); );
} }
// Update last index // Update last index
lastIndex = regex.lastIndex; lastIndex = regex.lastIndex;
} }
// Push text after the last match // Push text after the last match
if (lastIndex !== line.length) { if (lastIndex !== line.length) {
parts.push(line.substring(lastIndex)); parts.push(line.substring(lastIndex));
} }
return <p>{parts}</p>; return <p>{parts}</p>;
}) })
: chatMessage} : chatMessage}
</span> </span>
</> </>
) : ( ) : (
<span <div className="flex flex-col">
className="prose text-primary word-break-break-word dark:prose-invert" <span
data-testid={ className="prose text-primary word-break-break-word dark:prose-invert"
"chat-message-" + chat.sender_name + "-" + chatMessage data-testid={
} "chat-message-" + chat.sender_name + "-" + chatMessage
> }
{chatMessage} >
</span> {chatMessage}
</span>
{chat.files && (
<div className="my-2">
{chat.files.map((file, index) => {
return (
<div key={index} className="flex flex-col gap-2">
<span
onClick={() => setShowFile(!showFile)}
className="flex cursor-pointer gap-2 text-sm text-muted-foreground"
>
{file.name}
<ForwardedIconComponent
name={showFile ? "ChevronDown" : "ChevronRight"}
/>
</span>
<FileCard
showFile={showFile}
fileName={file.name}
fileType={file.type}
content={file.path}
/>
</div>
);
})}
</div>
)}
</div>
)} )}
</div> </div>
)} )}
</div> </div>
<div id={lastMessage ? "last-chat-message" : ""}></div> <div id={lastMessage ? "last-chat-message" : ""}></div>
{chat.files && (
<div className="my-2 w-full">
{chat.files.map((file, index) => {
return (
<div key={index} className="my-2 w-full">
<FileCard
fileName={file.name}
fileType={file.type}
content={file.path}
/>
</div>
);
})}
</div>
)}
</> </>
); );
} }

View file

@ -1,9 +1,12 @@
import * as base64js from "base64-js"; import * as base64js from "base64-js";
import { useState } from "react"; import { useState } from "react";
import IconComponent from "../../../../../components/genericIconComponent"; import IconComponent, {
ForwardedIconComponent,
} from "../../../../../components/genericIconComponent";
import { fileCardPropsType } from "../../../../../types/components"; import { fileCardPropsType } from "../../../../../types/components";
import useFlowsManagerStore from "../../../../../stores/flowsManagerStore"; import useFlowsManagerStore from "../../../../../stores/flowsManagerStore";
import { BACKEND_URL, BASE_URL_API } from "../../../../../constants/constants"; import { BACKEND_URL, BASE_URL_API } from "../../../../../constants/constants";
import formatFileName from "../filePreviewChat/utils/format-file-name";
const imgTypes = new Set(["png", "jpg"]); const imgTypes = new Set(["png", "jpg"]);
@ -11,10 +14,9 @@ export default function FileCard({
fileName, fileName,
content, content,
fileType, fileType,
}: fileCardPropsType): JSX.Element { showFile = true,
const handleDownload = (): void => { }: fileCardPropsType): JSX.Element | undefined {
//TODO: update download function const handleDownload = (): void => {};
};
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId); const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
function handleMouseEnter(): void { function handleMouseEnter(): void {
@ -24,58 +26,63 @@ export default function FileCard({
setIsHovered(false); setIsHovered(false);
} }
if (imgTypes.has(fileType)) { const imgSrc = `${BACKEND_URL.slice(0, BACKEND_URL.length - 1)}${BASE_URL_API}files/images/${content}`;
if (showFile) {
if (imgTypes.has(fileType)) {
return (
<div
className="inline-block w-full rounded-lg transition-all"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
style={{ display: "inline-block" }}
>
<div className="relative w-[80%] rounded-lg border border-border">
<img
src={imgSrc}
alt="generated image"
className="m-0 h-auto w-auto rounded-lg border border-border p-0 transition-all"
/>
{isHovered && (
<div
className={`absolute right-0 top-0 rounded-bl-lg bg-muted px-1 text-sm font-bold text-foreground `}
>
<button
className="px-2 py-1 text-ring "
onClick={handleDownload}
>
<IconComponent
name="DownloadCloud"
className="h-5 w-5 text-current hover:scale-110"
/>
</button>
</div>
)}
</div>
</div>
);
}
return ( return (
<div <div
className="relative ml-20 h-auto w-auto border border-border" className={`relative ${false ? "h-20 w-20" : "h-20 w-80"} cursor-pointer rounded-lg border border-ring bg-muted shadow transition duration-300 hover:drop-shadow-lg ${
onMouseEnter={handleMouseEnter} isHovered ? "shadow-md" : ""
onMouseLeave={handleMouseLeave} }`}
onClick={handleDownload}
> >
<img <div className="ml-3 flex h-full w-full items-center gap-2 text-sm">
src={`${BACKEND_URL.slice(0, BACKEND_URL.length - 1)}${BASE_URL_API}files/images/${content}`} <ForwardedIconComponent name="File" className="h-8 w-8" />
alt="generated image" <div className="flex flex-col">
className="h-auto w-auto rounded-lg border border-border" <span className="font-bold">{formatFileName(fileName, 20)}</span>
/> <span>File</span>
{isHovered && (
<div className={`file-card-modal-image-div `}>
<button
className="file-card-modal-image-button "
onClick={handleDownload}
>
<IconComponent
name="DownloadCloud"
className="h-5 w-5 text-current hover:scale-110"
/>
</button>
</div> </div>
)}
</div>
);
}
return (
<button onClick={handleDownload} className="file-card-modal-button">
<div className="file-card-modal-div">
{" "}
{imgTypes.has(fileType) ? (
<img
src={`${BACKEND_URL.slice(0, BACKEND_URL.length - 1)}${BASE_URL_API}files/images/${content}`}
alt=""
className="h-8 w-8"
/>
) : (
<IconComponent name="File" className="h-8 w-8" />
)}
<div className="file-card-modal-footer">
{" "}
<div className="file-card-modal-name">{fileName}</div>
<div className="file-card-modal-type">{fileType}</div>
</div> </div>
<IconComponent <IconComponent
name="DownloadCloud" name="DownloadCloud"
className="ml-auto h-6 w-6 text-current" className="absolute right-2 top-2 ml-auto h-6 w-6 text-current"
/> />
</div> </div>
</button> );
); }
return undefined;
} }

View file

@ -1,8 +1,11 @@
export default function formatFileName(name: string): string { export default function formatFileName(
name: string,
numberToTruncate: number = 29,
): string {
const fileExtension = name.split(".").pop(); // Get the file extension const fileExtension = name.split(".").pop(); // Get the file extension
const baseName = name.slice(0, name.lastIndexOf(".")); // Get the base name without the extension const baseName = name.slice(0, name.lastIndexOf(".")); // Get the base name without the extension
if (baseName.length > 6) { if (baseName.length > 6) {
return `${baseName.slice(0, 29)}...${fileExtension}`; return `${baseName.slice(0, numberToTruncate)}...${fileExtension}`;
} }
return name; return name;
} }

View file

@ -523,6 +523,7 @@ export type fileCardPropsType = {
fileName: string; fileName: string;
content: string; content: string;
fileType: string; fileType: string;
showFile?: boolean;
}; };
export type nodeToolbarPropsType = { export type nodeToolbarPropsType = {