import { ActionIcon, AppShell, Autocomplete, Box, Burger, Button, Group, Indicator, Loader, Pagination, Paper, Progress, ScrollArea, Select, Stack, Text, TextInput, Title, Tooltip, } from "@mantine/core"; import { Document, Page } from "react-pdf"; import { Form, isNotEmpty, useForm } from "@mantine/form"; import { IconDownload, IconEye, IconEyeOff, IconFiles, IconLayoutSidebarLeftCollapse, IconLayoutSidebarLeftExpand, IconRestore, IconRobotFace, IconTrash, } from "@tabler/icons-react"; import { createArchiveAndDownload, fileCategories, getFileDataUrl, systemMessage, } from "./utils"; import { useCallback, useMemo, useState } from "react"; import { ChatCompletionMessageParam } from "@mlc-ai/web-llm"; import Dropzone from "./Dropzone"; import { FileWithPath } from "@mantine/dropzone"; import LLMPicker from "./LLMPicker"; import classNames from "./Organizrr.module.css"; import { useDisclosure } from "@mantine/hooks"; import { useMLEngine } from "./MLEngineContext"; type FormValues = { customer: { firstName: string; lastName: string }; documents: { file: FileWithPath; id: string }[]; files: { id: string; documents: { id: string; selectedPages?: string }[]; suffix: string; }[]; }; function Organizrr() { const [mobileOpened, { toggle: toggleMobile, close: closeMobile }] = useDisclosure(true); const [desktopOpened, { close: closeDesktop, open: openDesktop }] = useDisclosure(true); const [previewMobileOpened, { toggle: togglePreviewMobile }] = useDisclosure(); const [previewOpened, { toggle: togglePreview, open: openPreview }] = useDisclosure(true); const { engine } = useMLEngine(); const [activeDocumentId, setActiveDocumentId] = useState(null); const [activeFile, setActiveFile] = useState(null); const [generatingFilenames, setGeneratingFilenames] = useState([]); const form = useForm({ mode: "controlled", initialValues: { customer: { firstName: "", lastName: "" }, documents: [], files: [], }, validate: { customer: { firstName: isNotEmpty("First name must be given"), lastName: isNotEmpty("Last name must be given"), }, files: { suffix: isNotEmpty("Suffix must be given"), documents: { id: isNotEmpty("Document must be selected"), }, }, }, validateInputOnBlur: true, }); const activeDocument = useMemo( () => activeDocumentId && form.values.documents.find((d) => d.id === activeDocumentId), [activeDocumentId, form] ); const [numPages, setNumPages] = useState(); const [pageNumber, setPageNumber] = useState(1); const onDocumentLoadSuccess = useCallback( ({ numPages }: { numPages: number }) => { setNumPages(numPages); }, [setNumPages] ); const handleFileDrop = (files: FileWithPath[]) => { if (files.length < 1) return; files.forEach((f) => { const id = Math.random().toString(36).replace("0.", "doc_"); const fileId = Math.random().toString(36).replace("0.", "file_"); form.insertListItem("documents", { id, file: f }); form.insertListItem("files", { id: fileId, documents: [{ id }], suffix: "", }); const messages: ChatCompletionMessageParam[] = [ systemMessage, { role: "user", content: "The file name is: " + f.name }, ]; setGeneratingFilenames((fns) => [...fns, fileId]); engine.current?.chat.completions .create({ messages, }) .then((reply) => { if ( reply && reply.choices[0].message.content && !reply?.choices[0].message.content?.includes( "Unable to label file" ) && !reply?.choices[0].message.content?.includes("I'm sorry") ) { console.log(reply?.choices[0].message.content); form.getValues().files.forEach((f, idx) => { if (f.id === fileId) { form.setFieldValue( `files.${idx}.suffix`, reply?.choices[0].message.content?.split("\n")[0] ); } }); } else { console.warn(reply?.choices[0].message.content); } setGeneratingFilenames((fns) => fns.filter((fn) => fn !== fileId)); }) .catch((e) => { console.error(e); setGeneratingFilenames((fns) => fns.filter((fn) => fn !== fileId)); }); }); setActiveFile(form.getValues().files.length - 1); setActiveDocumentId( form.getValues().documents[form.getValues().documents.length - 1].id ); }; const handleDocumentDrop = (files: FileWithPath[]) => { if (activeFile === null) return; files.forEach((f) => { const id = Math.random().toString(36).replace("0.", "doc_"); form.insertListItem("documents", { id, file: f }); form.insertListItem(`files.${activeFile}.documents`, { id }); }); setActiveDocumentId( form.getValues().documents[form.getValues().documents.length - 1].id ); }; const handleDocumentSelect = (id: string | null) => { if (id === null) return; if (activeFile === null) return; form.insertListItem(`files.${activeFile}.documents`, { id }); setActiveDocumentId(id); }; const handleSubmit = async ({ customer, files, documents }: FormValues) => { await createArchiveAndDownload(async () => { return { customer, files: files.map((f) => ({ ...f, documents: f.documents.map((d) => { const selectedPages = d.selectedPages ?.split(",") .map((sp) => sp.replace(" ", "")); return { ...d, selectedPages: selectedPages?.length ? selectedPages : [], }; }), })), documents: await Promise.all( documents.map(async (d) => ({ ...d, name: d.file.name, blob: await getFileDataUrl(d.file), })) ), }; }); }; const handleErrors = (errors: typeof form.errors) => { for (const [key] of Object.entries(errors)) { if (key.startsWith("files.")) { if (!Number.isNaN(key.split(".")[1])) { const idx = parseInt(key.split(".")[1]); setActiveFile(idx); break; } } } }; return (
Organizrr By InnoPeak {previewOpened ? : } {previewMobileOpened ? : } Files {form.values.files.map((f, idx) => ( {activeFile != idx && form.errors && Object.entries(form.errors) .filter(([key]) => key.startsWith(`files.${idx}`)) .map(([_, error]) => {error})} ))} Customer { form.reset(); setActiveFile(null); }} > {activeFile !== null && ( Documents {form.values.files[activeFile]?.documents.map( (d, idx) => ( { setActiveDocumentId(d.id); openPreview(); }} > { form.values.documents.find( (_d) => _d.id === d.id )?.file.name } { e.stopPropagation(); form.removeListItem( `files.${activeFile}.documents`, idx ); }} > {form.values.documents .find((_d) => _d.id === d.id) ?.file.name.endsWith(".pdf") && ( _d.id === d.id )?.file } className={classNames.pdfPreview} > )} e.stopPropagation()} {...form.getInputProps( `files.${activeFile}.documents.${idx}.selectedPages` )} key={form.key( `files.${activeFile}.documents.${idx}.selectedPages` )} /> ) )} {form.values.documents .find( (doc) => doc.id === form.values.files[activeFile]?.documents[0].id ) ?.file.name.endsWith(".pdf") && (