mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2024-11-15 02:50:10 +01:00
Add drag-and-drop to chat window (#1995)
* Add drag-and-drop to chat window * add uploader icon and remove empty space text when attachments are present * color theme * color update
This commit is contained in:
parent
5e73dce506
commit
d877d2b7ad
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -39,11 +39,13 @@
|
||||
"openrouter",
|
||||
"pagerender",
|
||||
"Qdrant",
|
||||
"royalblue",
|
||||
"searxng",
|
||||
"Serper",
|
||||
"Serply",
|
||||
"textgenwebui",
|
||||
"togetherai",
|
||||
"Unembed",
|
||||
"vectordbs",
|
||||
"Weaviate",
|
||||
"Zilliz"
|
||||
|
@ -17,6 +17,7 @@ export default function ChatHistory({
|
||||
sendCommand,
|
||||
updateHistory,
|
||||
regenerateAssistantMessage,
|
||||
hasAttachments = false,
|
||||
}) {
|
||||
const { user } = useUser();
|
||||
const { threadSlug = null } = useParams();
|
||||
@ -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">
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
@ -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>
|
||||
);
|
||||
}
|
@ -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 };
|
||||
}
|
||||
}
|
@ -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({
|
||||
@ -21,6 +22,7 @@ export default function PromptInput({
|
||||
inputDisabled,
|
||||
buttonDisabled,
|
||||
sendCommand,
|
||||
attachments = [],
|
||||
}) {
|
||||
const [promptInput, setPromptInput] = useState("");
|
||||
const { showAgents, setShowAgents } = useAvailableAgents();
|
||||
@ -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}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import ChatHistory from "./ChatHistory";
|
||||
import DnDFileUploadWrapper, { CLEAR_ATTACHMENTS_EVENT } from "./DnDWrapper";
|
||||
import PromptInput, { PROMPT_INPUT_EVENT } from "./PromptInput";
|
||||
import Workspace from "@/models/workspace";
|
||||
import handleChat, { ABORT_STREAM_EVENT } from "@/utils/chat";
|
||||
@ -121,13 +122,13 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Simplify this
|
||||
if (!promptMessage || !promptMessage?.userMessage) return false;
|
||||
if (!!threadSlug) {
|
||||
await Workspace.threads.streamChat(
|
||||
{ workspaceSlug: workspace.slug, threadSlug },
|
||||
promptMessage.userMessage,
|
||||
(chatResult) =>
|
||||
window.dispatchEvent(new CustomEvent(CLEAR_ATTACHMENTS_EVENT));
|
||||
await Workspace.multiplexStream({
|
||||
workspaceSlug: workspace.slug,
|
||||
threadSlug,
|
||||
prompt: promptMessage.userMessage,
|
||||
chatHandler: (chatResult) =>
|
||||
handleChat(
|
||||
chatResult,
|
||||
setLoadingResponse,
|
||||
@ -135,23 +136,8 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
|
||||
remHistory,
|
||||
_chatHistory,
|
||||
setSocketId
|
||||
)
|
||||
);
|
||||
} else {
|
||||
await Workspace.streamChat(
|
||||
workspace,
|
||||
promptMessage.userMessage,
|
||||
(chatResult) =>
|
||||
handleChat(
|
||||
chatResult,
|
||||
setLoadingResponse,
|
||||
setChatHistory,
|
||||
remHistory,
|
||||
_chatHistory,
|
||||
setSocketId
|
||||
)
|
||||
);
|
||||
}
|
||||
),
|
||||
});
|
||||
return;
|
||||
}
|
||||
loadingResponse === true && fetchReply();
|
||||
@ -205,6 +191,7 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
|
||||
});
|
||||
setWebsocket(socket);
|
||||
window.dispatchEvent(new CustomEvent(AGENT_SESSION_START));
|
||||
window.dispatchEvent(new CustomEvent(CLEAR_ATTACHMENTS_EVENT));
|
||||
} catch (e) {
|
||||
setChatHistory((prev) => [
|
||||
...prev.filter((msg) => !!msg.content),
|
||||
@ -234,13 +221,16 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
|
||||
className="transition-all duration-500 relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] bg-main-gradient w-full h-full overflow-y-scroll border-2 border-outline"
|
||||
>
|
||||
{isMobile && <SidebarMobileHeader />}
|
||||
<div className="flex flex-col h-full w-full md:mt-0 mt-[40px]">
|
||||
<DnDFileUploadWrapper workspace={workspace}>
|
||||
{(files) => (
|
||||
<>
|
||||
<ChatHistory
|
||||
history={chatHistory}
|
||||
workspace={workspace}
|
||||
sendCommand={sendCommand}
|
||||
updateHistory={setChatHistory}
|
||||
regenerateAssistantMessage={regenerateAssistantMessage}
|
||||
hasAttachments={files.length > 0}
|
||||
/>
|
||||
<PromptInput
|
||||
submit={handleSubmit}
|
||||
@ -248,8 +238,11 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
|
||||
inputDisabled={loadingResponse}
|
||||
buttonDisabled={loadingResponse}
|
||||
sendCommand={sendCommand}
|
||||
attachments={files}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</DnDFileUploadWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -110,6 +110,20 @@ const Workspace = {
|
||||
);
|
||||
return this._updateChatResponse(slug, chatId, newText);
|
||||
},
|
||||
multiplexStream: async function ({
|
||||
workspaceSlug,
|
||||
threadSlug = null,
|
||||
prompt,
|
||||
chatHandler,
|
||||
}) {
|
||||
if (!!threadSlug)
|
||||
return this.threads.streamChat(
|
||||
{ workspaceSlug, threadSlug },
|
||||
prompt,
|
||||
chatHandler
|
||||
);
|
||||
return this.streamChat({ slug: workspaceSlug }, prompt, chatHandler);
|
||||
},
|
||||
streamChat: async function ({ slug }, message, handleChat) {
|
||||
const ctrl = new AbortController();
|
||||
|
||||
@ -411,6 +425,43 @@ const Workspace = {
|
||||
return null;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Uploads and embeds a single file in a single call into a workspace
|
||||
* @param {string} slug - workspace slug
|
||||
* @param {FormData} formData
|
||||
* @returns {Promise<{response: {ok: boolean}, data: {success: boolean, error: string|null, document: {id: string, location:string}|null}}>}
|
||||
*/
|
||||
uploadAndEmbedFile: async function (slug, formData) {
|
||||
const response = await fetch(
|
||||
`${API_BASE}/workspace/${slug}/upload-and-embed`,
|
||||
{
|
||||
method: "POST",
|
||||
body: formData,
|
||||
headers: baseHeaders(),
|
||||
}
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
return { response, data };
|
||||
},
|
||||
|
||||
/**
|
||||
* Deletes and un-embeds a single file in a single call from a workspace
|
||||
* @param {string} slug - workspace slug
|
||||
* @param {string} documentLocation - location of file eg: custom-documents/my-file-uuid.json
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
deleteAndUnembedFile: async function (slug, documentLocation) {
|
||||
const response = await fetch(
|
||||
`${API_BASE}/workspace/${slug}/remove-and-unembed`,
|
||||
{
|
||||
method: "DELETE",
|
||||
body: JSON.stringify({ documentLocation }),
|
||||
headers: baseHeaders(),
|
||||
}
|
||||
);
|
||||
return response.ok;
|
||||
},
|
||||
threads: WorkspaceThread,
|
||||
};
|
||||
|
||||
|
@ -36,6 +36,11 @@ export default {
|
||||
"dark-text": "#222628",
|
||||
description: "#D2D5DB",
|
||||
"x-button": "#9CA3AF",
|
||||
royalblue: '#3538CD',
|
||||
magenta: '#C11574',
|
||||
danger: '#F04438',
|
||||
warn: '#854708',
|
||||
success: '#027A48',
|
||||
darker: "#F4F4F4"
|
||||
},
|
||||
backgroundImage: {
|
||||
|
@ -33,6 +33,7 @@ const {
|
||||
const { getTTSProvider } = require("../utils/TextToSpeech");
|
||||
const { WorkspaceThread } = require("../models/workspaceThread");
|
||||
const truncate = require("truncate");
|
||||
const { purgeDocument } = require("../utils/files/purgeDocument");
|
||||
|
||||
function workspaceEndpoints(app) {
|
||||
if (!app) return;
|
||||
@ -863,6 +864,114 @@ function workspaceEndpoints(app) {
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/** Handles the uploading and embedding in one-call by uploading via drag-and-drop in chat container. */
|
||||
app.post(
|
||||
"/workspace/:slug/upload-and-embed",
|
||||
[
|
||||
validatedRequest,
|
||||
flexUserRoleValid([ROLES.admin, ROLES.manager]),
|
||||
handleFileUpload,
|
||||
],
|
||||
async function (request, response) {
|
||||
try {
|
||||
const { slug = null } = request.params;
|
||||
const user = await userFromSession(request, response);
|
||||
const currWorkspace = multiUserMode(response)
|
||||
? await Workspace.getWithUser(user, { slug })
|
||||
: await Workspace.get({ slug });
|
||||
|
||||
if (!currWorkspace) {
|
||||
response.sendStatus(400).end();
|
||||
return;
|
||||
}
|
||||
|
||||
const Collector = new CollectorApi();
|
||||
const { originalname } = request.file;
|
||||
const processingOnline = await Collector.online();
|
||||
|
||||
if (!processingOnline) {
|
||||
response
|
||||
.status(500)
|
||||
.json({
|
||||
success: false,
|
||||
error: `Document processing API is not online. Document ${originalname} will not be processed automatically.`,
|
||||
})
|
||||
.end();
|
||||
return;
|
||||
}
|
||||
|
||||
const { success, reason, documents } =
|
||||
await Collector.processDocument(originalname);
|
||||
if (!success || documents?.length === 0) {
|
||||
response.status(500).json({ success: false, error: reason }).end();
|
||||
return;
|
||||
}
|
||||
|
||||
Collector.log(
|
||||
`Document ${originalname} uploaded processed and successfully. It is now available in documents.`
|
||||
);
|
||||
await Telemetry.sendTelemetry("document_uploaded");
|
||||
await EventLogs.logEvent(
|
||||
"document_uploaded",
|
||||
{
|
||||
documentName: originalname,
|
||||
},
|
||||
response.locals?.user?.id
|
||||
);
|
||||
|
||||
const document = documents[0];
|
||||
const { failedToEmbed = [], errors = [] } = await Document.addDocuments(
|
||||
currWorkspace,
|
||||
[document.location],
|
||||
response.locals?.user?.id
|
||||
);
|
||||
|
||||
if (failedToEmbed.length > 0)
|
||||
return response
|
||||
.status(200)
|
||||
.json({ success: false, error: errors?.[0], document: null });
|
||||
|
||||
response.status(200).json({
|
||||
success: true,
|
||||
error: null,
|
||||
document: { id: document.id, location: document.location },
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e.message, e);
|
||||
response.sendStatus(500).end();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.delete(
|
||||
"/workspace/:slug/remove-and-unembed",
|
||||
[
|
||||
validatedRequest,
|
||||
flexUserRoleValid([ROLES.admin, ROLES.manager]),
|
||||
handleFileUpload,
|
||||
],
|
||||
async function (request, response) {
|
||||
try {
|
||||
const { slug = null } = request.params;
|
||||
const body = reqBody(request);
|
||||
const user = await userFromSession(request, response);
|
||||
const currWorkspace = multiUserMode(response)
|
||||
? await Workspace.getWithUser(user, { slug })
|
||||
: await Workspace.get({ slug });
|
||||
|
||||
if (!currWorkspace || !body.documentLocation)
|
||||
return response.sendStatus(400).end();
|
||||
|
||||
// Will delete the document from the entire system + wil unembed it.
|
||||
await purgeDocument(body.documentLocation);
|
||||
response.status(200).end();
|
||||
} catch (e) {
|
||||
console.error(e.message, e);
|
||||
response.sendStatus(500).end();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = { workspaceEndpoints };
|
||||
|
Loading…
Reference in New Issue
Block a user