θΏ™ζ˜―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
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@
"openrouter",
"pagerender",
"Qdrant",
"royalblue",
"searxng",
"Serper",
"Serply",
"textgenwebui",
"togetherai",
"Unembed",
"vectordbs",
"Weaviate",
"Zilliz"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default function ChatHistory({
sendCommand,
updateHistory,
regenerateAssistantMessage,
hasAttachments = false,
}) {
const { user } = useUser();
const { threadSlug = null } = useParams();
Expand Down Expand Up @@ -144,7 +145,7 @@ export default function ChatHistory({
);
};

if (history.length === 0) {
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">
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { useState, useEffect } from "react";
import { v4 } from "uuid";
import System from "@/models/system";
import { useDropzone } from "react-dropzone";
import DndIcon from "./dnd-icon.png";
import Workspace from "@/models/workspace";
import useUser from "@/hooks/useUser";

export const REMOVE_ATTACHMENT_EVENT = "ATTACHMENT_REMOVE";
export const CLEAR_ATTACHMENTS_EVENT = "ATTACHMENT_CLEAR";

/**
* File Attachment for automatic upload on the chat container page.
* @typedef Attachment
* @property {string} uid - unique file id.
* @property {File} file - native File object
* @property {('in_progress'|'failed'|'success')} status - the automatic upload status.
* @property {string|null} error - Error message
* @property {{id:string, location:string}|null} document - uploaded document details
*/

export default function DnDFileUploaderWrapper({ workspace, children }) {
/** @type {[Attachment[], Function]} */
const [files, setFiles] = useState([]);
const [ready, setReady] = useState(false);
const [dragging, setDragging] = useState(false);
const { user } = useUser();

useEffect(() => {
if (!!user && user.role === "default") return false;
System.checkDocumentProcessorOnline().then((status) => setReady(status));
}, [user]);

useEffect(() => {
window.addEventListener(REMOVE_ATTACHMENT_EVENT, handleRemove);
window.addEventListener(CLEAR_ATTACHMENTS_EVENT, resetAttachments);

return () => {
window.removeEventListener(REMOVE_ATTACHMENT_EVENT, handleRemove);
window.removeEventListener(CLEAR_ATTACHMENTS_EVENT, resetAttachments);
};
}, []);

/**
* Remove file from uploader queue.
* @param {CustomEvent<{uid: string}>} event
*/
async function handleRemove(event) {
/** @type {{uid: Attachment['uid'], document: Attachment['document']}} */
const { uid, document } = event.detail;
setFiles((prev) => prev.filter((prevFile) => prevFile.uid !== uid));
if (!document.location) return;
await Workspace.deleteAndUnembedFile(workspace.slug, document.location);
}

/**
* Clear queue of attached files currently in prompt box
*/
function resetAttachments() {
setFiles([]);
}

async function onDrop(acceptedFiles, _rejections) {
setDragging(false);
/** @type {Attachment[]} */
const newAccepted = acceptedFiles.map((file) => {
return {
uid: v4(),
file,
status: "in_progress",
error: null,
};
});
setFiles((prev) => [...prev, ...newAccepted]);

for (const attachment of newAccepted) {
const formData = new FormData();
formData.append("file", attachment.file, attachment.file.name);
Workspace.uploadAndEmbedFile(workspace.slug, formData).then(
({ response, data }) => {
const updates = {
status: response.ok ? "success" : "failed",
error: data?.error ?? null,
document: data?.document,
};

setFiles((prev) => {
return prev.map(
(
/** @type {Attachment} */
prevFile
) => {
if (prevFile.uid !== attachment.uid) return prevFile;
return { ...prevFile, ...updates };
}
);
});
}
);
}
}

const { getRootProps, getInputProps } = useDropzone({
onDrop,
disabled: !ready,
noClick: true,
noKeyboard: true,
onDragEnter: () => setDragging(true),
onDragLeave: () => setDragging(false),
});

return (
<div
className={`relative flex flex-col h-full w-full md:mt-0 mt-[40px] p-[1px]`}
{...getRootProps()}
>
<div
hidden={!dragging}
className="absolute top-0 w-full h-full bg-dark-text/90 rounded-2xl border-[4px] border-white z-[9999]"
>
<div className="w-full h-full flex justify-center items-center rounded-xl">
<div className="flex flex-col gap-y-[14px] justify-center items-center">
<img src={DndIcon} width={69} height={69} />
<p className="text-white text-[24px] font-semibold">Add anything</p>
<p className="text-white text-[16px] text-center">
Drop your file here to embed it into your <br />
workspace auto-magically.
</p>
</div>
</div>
</div>
<input {...getInputProps()} />
{children(files, setFiles)}
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import {
CircleNotch,
FileCode,
FileCsv,
FileDoc,
FileHtml,
FilePdf,
WarningOctagon,
X,
} from "@phosphor-icons/react";
import { humanFileSize } from "@/utils/numbers";
import { FileText } from "@phosphor-icons/react/dist/ssr";
import { REMOVE_ATTACHMENT_EVENT } from "../../DnDWrapper";
import { Tooltip } from "react-tooltip";

/**
* @param {{attachments: import("../../DnDWrapper").Attachment[]}}
* @returns
*/
export default function AttachmentManager({ attachments }) {
if (attachments.length === 0) return null;
return (
<div className="flex flex-wrap my-2">
{attachments.map((attachment) => (
<AttachmentItem key={attachment.uid} attachment={attachment} />
))}
</div>
);
}

/**
* @param {{attachment: import("../../DnDWrapper").Attachment}}
*/
function AttachmentItem({ attachment }) {
const { uid, file, status, error, document } = attachment;
const { iconBgColor, Icon } = displayFromFile(file);

function removeFileFromQueue() {
window.dispatchEvent(
new CustomEvent(REMOVE_ATTACHMENT_EVENT, { detail: { uid, document } })
);
}

if (status === "in_progress") {
return (
<div
className={`h-14 px-2 py-2 flex items-center gap-x-4 rounded-lg bg-zinc-800 border border-white/20 w-[200px]`}
>
<div
className={`${iconBgColor} rounded-lg flex items-center justify-center flex-shrink-0 p-1`}
>
<CircleNotch size={30} className="text-white animate-spin" />
</div>
<div className="flex flex-col w-[130px]">
<p className="text-white text-xs font-medium truncate">{file.name}</p>
<p className="text-white/60 text-xs font-medium">
{humanFileSize(file.size)}
</p>
</div>
</div>
);
}

if (status === "failed") {
return (
<>
<div
data-tooltip-id={`attachment-uid-${uid}-error`}
data-tooltip-content={error}
className={`relative h-14 px-2 py-2 flex items-center gap-x-4 rounded-lg bg-[#4E140B] border border-transparent w-[200px] group`}
>
<div className="invisible group-hover:visible absolute -top-[5px] -right-[5px] w-fit h-fit z-[10]">
<button
onClick={removeFileFromQueue}
type="button"
className="bg-zinc-700 hover:bg-red-400 rounded-full p-1 flex items-center justify-center hover:border-transparent border border-white/40"
>
<X
size={10}
className="flex-shrink-0 text-zinc-200 group-hover:text-white"
/>
</button>
</div>
<div
className={`bg-danger rounded-lg flex items-center justify-center flex-shrink-0 p-1`}
>
<WarningOctagon size={30} className="text-white" />
</div>
<div className="flex flex-col w-[130px]">
<p className="text-white text-xs font-medium truncate">
{file.name}
</p>
<p className="text-red-100 text-xs truncate">
{error ?? "this file failed to upload"}. It will not be available
in the workspace.
</p>
</div>
</div>
<Tooltip
id={`attachment-uid-${uid}-error`}
place="top"
delayShow={300}
className="allm-tooltip !allm-text-xs"
/>
</>
);
}

return (
<>
<div
data-tooltip-id={`attachment-uid-${uid}-success`}
data-tooltip-content={`${file.name} was uploaded and embedded into this workspace. It will be available for RAG chat now.`}
className={`relative h-14 px-2 py-2 flex items-center gap-x-4 rounded-lg bg-zinc-800 border border-white/20 w-[200px] group`}
>
<div className="invisible group-hover:visible absolute -top-[5px] -right-[5px] w-fit h-fit z-[10]">
<button
onClick={removeFileFromQueue}
type="button"
className="bg-zinc-700 hover:bg-red-400 rounded-full p-1 flex items-center justify-center hover:border-transparent border border-white/40"
>
<X
size={10}
className="flex-shrink-0 text-zinc-200 group-hover:text-white"
/>
</button>
</div>
<div
className={`${iconBgColor} rounded-lg flex items-center justify-center flex-shrink-0 p-1`}
>
<Icon size={30} className="text-white" />
</div>
<div className="flex flex-col w-[130px]">
<p className="text-white text-xs font-medium truncate">{file.name}</p>
<p className="text-white/80 text-xs font-medium">File embedded!</p>
</div>
</div>
<Tooltip
id={`attachment-uid-${uid}-success`}
place="top"
delayShow={300}
className="allm-tooltip !allm-text-xs"
/>
</>
);
}

/**
* @param {File} file
* @returns {{iconBgColor:string, Icon: React.Component}}
*/
function displayFromFile(file) {
const extension = file?.name?.split(".")?.pop()?.toLowerCase() ?? "txt";
switch (extension) {
case "pdf":
return { iconBgColor: "bg-magenta", Icon: FilePdf };
case "doc":
case "docx":
return { iconBgColor: "bg-royalblue", Icon: FileDoc };
case "html":
return { iconBgColor: "bg-warn", Icon: FileHtml };
case "csv":
case "xlsx":
return { iconBgColor: "bg-success", Icon: FileCsv };
case "json":
case "sql":
case "js":
case "jsx":
case "cpp":
case "c":
case "c":
return { iconBgColor: "bg-warn", Icon: FileCode };
default:
return { iconBgColor: "bg-royalblue", Icon: FileText };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import AvailableAgentsButton, {
import TextSizeButton from "./TextSizeMenu";
import SpeechToText from "./SpeechToText";
import { Tooltip } from "react-tooltip";
import AttachmentManager from "./Attachments";

export const PROMPT_INPUT_EVENT = "set_prompt_input";
export default function PromptInput({
Expand All @@ -21,6 +22,7 @@ export default function PromptInput({
inputDisabled,
buttonDisabled,
sendCommand,
attachments = [],
}) {
const [promptInput, setPromptInput] = useState("");
const { showAgents, setShowAgents } = useAvailableAgents();
Expand Down Expand Up @@ -106,10 +108,11 @@ export default function PromptInput({
/>
<form
onSubmit={handleSubmit}
className="flex flex-col gap-y-1 rounded-t-lg md:w-3/4 w-full mx-auto max-w-xl"
className="flex flex-col gap-y-1 rounded-t-lg md:w-3/4 w-full mx-auto max-w-xl items-center"
>
<div className="flex items-center rounded-lg md:mb-4">
<div className="w-[600px] bg-main-gradient shadow-2xl border border-white/50 rounded-2xl flex flex-col px-4 overflow-hidden">
<div className="w-[635px] bg-main-gradient shadow-2xl border border-white/50 rounded-2xl flex flex-col px-4 overflow-hidden">
<AttachmentManager attachments={attachments} />
<div className="flex items-center w-full border-b-2 border-gray-500/50">
<textarea
ref={textareaRef}
Expand Down
Loading