slow wait and save

This commit is contained in:
Dhravya Shah 2024-08-09 23:16:23 -07:00
parent 12aa2e0832
commit 0cc9069091
12 changed files with 4800 additions and 27 deletions

View file

@ -21,6 +21,7 @@ import { useToast } from "./ui/shadcn/use-toast";
import { Input } from "./ui/shadcn/input";
import { Label } from "./ui/shadcn/label";
import { Textarea } from "./ui/shadcn/textarea";
import ShowCommandMenu from "./showCommandMenu";
const BACKEND_URL = "https://supermemory.ai";
@ -96,6 +97,52 @@ export default function ContentApp({
}
};
const [timer, setTimer] = useState(null);
const [progress, setProgress] = useState(0);
const timerRef = useRef(null);
useEffect(() => {
if (isPopoverOpen && !timer) {
startTimer();
}
}, [isPopoverOpen]);
const startTimer = () => {
setProgress(0);
// @ts-ignore
timerRef.current = setInterval(() => {
setProgress((prev) => {
if (prev >= 100) {
clearInterval(timerRef.current!);
saveContent();
return prev;
}
return prev + 5;
});
}, 100);
};
const stopTimer = () => {
clearInterval(timerRef.current!);
setTimer(null);
};
const saveContent = async () => {
await sendUrlToAPI(selectedSpace ? [selectedSpace] : []);
};
const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
setWebNote(e.target.value);
stopTimer();
};
const handleSelectChange = (value: string) => {
setSelectedSpace(value);
stopTimer();
};
useEffect(() => {
document.addEventListener("mousemove", (e) => {
const percentageX = (e.clientX / window.innerWidth) * 100;
@ -309,7 +356,10 @@ export default function ContentApp({
</div>
) : (
<div className="flex flex-col gap-2">
<Select onValueChange={(value) => setSelectedSpace(value)}>
<span className="text-xl text-white">
Saving to supermemory.ai
</span>
<Select onValueChange={handleSelectChange}>
<SelectTrigger className="text-white">
<SelectValue
className="placeholder:font-semibold placeholder:text-white"
@ -333,7 +383,7 @@ export default function ContentApp({
</Label>
<Textarea
value={webNote}
onChange={(e) => setWebNote(e.target.value)}
onChange={handleInputChange}
placeholder="Add a note"
className="text-white"
id="input-note"
@ -351,6 +401,12 @@ export default function ContentApp({
?.name
: "supermemory.ai"}
</button>
<div className="relative h-1 w-full bg-gray-300 mt-2">
<div
className="absolute h-1 bg-blue-600"
style={{ width: `${progress}%` }}
></div>
</div>
</div>
)}
</PopoverContent>
@ -442,6 +498,10 @@ export default function ContentApp({
</PopoverContent>
</Popover>
)}
{/*
<div className="flex min-h-screen w-screen top-0 left-0 bg-black/50 backdrop-blur-sm items-center justify-center">
<ShowCommandMenu />
</div> */}
</div>
);
}

View file

@ -0,0 +1,82 @@
import React, { useEffect } from "react";
import useCommandStore from "./store";
import { Command } from "cmdk";
import CommandSearch from "./ui/components/command/CommandSearch";
import Footer from "./ui/components/command/Footer";
import AiSearchView from "./ui/components/command/AiSearch";
function ShowCommandMenu() {
const {
currValue,
page,
pages,
search,
setSearch,
searchInputRef,
setCurrentValue,
setPage,
backPage,
} = useCommandStore();
const excludeFromDefaultActions = [""];
useEffect(() => {
setPage(pages[pages.length - 1]);
}, [pages]);
// continue typing when key is pressed
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (searchInputRef.current) {
// check if it is character or number
if (e.key.length === 1) {
searchInputRef.current.focus();
}
}
};
document.addEventListener("keydown", down);
return () => document.removeEventListener("keydown", down);
}, []);
// clear search when page changes
useEffect(() => {
if (page && !excludeFromDefaultActions.includes(page)) setSearch("");
}, [page]);
const handleKeyDown = (e: React.KeyboardEvent) => {
// Escape goes to previous page
// Backspace goes to previous page when search is empty
if (e.key === "Escape" || (e.key === "Backspace" && !search)) {
e.preventDefault();
backPage();
}
};
return (
<Command
// loop
value={currValue}
onValueChange={setCurrentValue}
defaultValue={"Projected revenue for this quarter?"}
onKeyDown={handleKeyDown}
className="bg-main w-full max-w-[782px] h-[514px] rounded-3xl outline outline-[2px] outline-outline overflow-hidden flex flex-col backdrop-blur-[160px]"
>
<div className="flex flex-col h-full">
{/* search */}
<CommandSearch />
{/* views */}
<Command.List className="h-full p-2 py-4 overflow-y-auto command-scrollbar my-2">
<AiSearchView />
</Command.List>
{/* bottom bar */}
<div className="bg-footer h-12 w-full shrink-0 p-4 text-xs font-medium">
<Footer />
</div>
</div>
</Command>
);
}
export default ShowCommandMenu;

View file

@ -0,0 +1,47 @@
import { create } from "zustand";
import { RefObject } from "react";
interface CommandProps {
currValue: string;
setCurrentValue: (value: string) => void;
search: string;
setSearch: (value: string) => void;
pages: string[];
setPages: (pages: string[]) => void;
backPage: () => void;
page: string;
setPage: (page: string) => void;
searchInputRef: RefObject<HTMLInputElement>;
setSearchInputRef: (ref: RefObject<HTMLInputElement>) => void;
}
const useCommandStore = create<CommandProps>((set) => ({
currValue: "",
setCurrentValue: (value) => set({ currValue: value }),
search: "",
setSearch: (value) => set({ search: value }),
pages: [],
setPages: (pages) => {
set({ pages });
},
backPage: () => {
const pages = [...useCommandStore.getState().pages];
pages.pop();
useCommandStore.getState().setPages(pages);
},
page: "",
setPage: (page) => set({ page }),
searchInputRef: { current: null },
setSearchInputRef: (ref: RefObject<HTMLInputElement>) =>
set({ searchInputRef: ref }),
}));
export default useCommandStore;

View file

@ -0,0 +1,156 @@
"use client";
import * as React from "react";
import { type DialogProps } from "@radix-ui/react-dialog";
import { Command as CommandPrimitive } from "cmdk";
// import { Search } from "lucide-react"
import { MagnifyingGlassCircleIcon as Search } from "@heroicons/react/24/outline";
import { cn } from "mxcn";
// import { Dialog, DialogContent } from "@/components/ui/dialog"
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className,
)}
{...props}
/>
));
Command.displayName = CommandPrimitive.displayName;
// interface CommandDialogProps extends DialogProps {}
// const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
// return (
// <Dialog {...props}>
// <DialogContent className="overflow-hidden p-0 shadow-lg">
// <Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
// {children}
// </Command>
// </DialogContent>
// </Dialog>
// )
// }
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
{...props}
/>
</div>
));
CommandInput.displayName = CommandPrimitive.Input.displayName;
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...props}
/>
));
CommandList.displayName = CommandPrimitive.List.displayName;
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-sm"
{...props}
/>
));
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
className,
)}
{...props}
/>
));
CommandGroup.displayName = CommandPrimitive.Group.displayName;
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn("-mx-1 h-px bg-border", className)}
{...props}
/>
));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className,
)}
{...props}
/>
));
CommandItem.displayName = CommandPrimitive.Item.displayName;
const CommandShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className,
)}
{...props}
/>
);
};
CommandShortcut.displayName = "CommandShortcut";
export {
Command,
// CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
};

View file

@ -0,0 +1,72 @@
import { Command } from "cmdk";
import CommandItem from "./Item";
import { ClockIcon, CloudIcon } from "@heroicons/react/16/solid";
import { GroupLabel } from "./GroupLabel";
import useCommandStore from "../../../store";
import { ArrowPathIcon, ClipboardIcon } from "@heroicons/react/24/outline";
function AiSearchView() {
const pages = useCommandStore((state) => state.pages);
const setPages = useCommandStore((state) => state.setPages);
return (
<div className="space-y-5">
{/* section */}
<div className="">
<div className="flex justify-between">
<GroupLabel>Model: Claude 3.5</GroupLabel>
<GroupLabel>response time: 2.1s</GroupLabel>
</div>
{/* response */}
<div className="space-y-5 px-2">
{/* text response */}
<div>
<p>{`Based on the current financial data and market trends, the projected revenue for this quarter is estimated to be around $11.2 million. This represents a 12% increase compared to the previous quarter, driven by strong demand for our new product line and expanded customer base. However, it's important to note that these are projections and the actual revenue may vary depending on various market conditions and unforeseen circumstances.`}</p>
</div>
{/* response actions */}
<div className="space-x-4">
<button className="text-icon hover:text-white duration-200">
<ClipboardIcon className="size-4" />
</button>
<button className="text-icon hover:text-white duration-200">
<ArrowPathIcon className="size-4" />
</button>
</div>
</div>
</div>
<div className="pt-5">
{/* <div className='flex justify-between'> */}
<GroupLabel>Chart visualisation</GroupLabel>
{/* <GroupLabel>response time: 2.1s</GroupLabel> */}
{/* </div> */}
{/* response */}
<div className="px-2 flex gap-6">
{/* insights */}
<div className="py-2 flex flex-col gap-4">
<div className="flex justify-between gap-8">
<p>Growth from previous quarter:</p>
<p>+12%</p>
</div>
<div className="flex justify-between gap-8">
<p>Month on month growth:</p>
<p>+86.67%</p>
</div>
<div className="flex justify-between gap-8">
<p>Growth from past year:</p>
<p className="text-red-400">-25.23%</p>
</div>
</div>
</div>
</div>
</div>
);
}
export default AiSearchView;

View file

@ -0,0 +1,63 @@
import { AnimatePresence, motion } from "framer-motion";
import { ArrowLeftIcon } from "@heroicons/react/24/outline";
import useCommandStore from "../../../store";
import { CommandInput } from "cmdk";
function CommandSearch() {
const { page, search, searchInputRef, setSearch, goBack } = useCommandStore(
(state) => {
return {
page: state.page,
setPages: state.setPages,
search: state.search,
setSearch: state.setSearch,
searchInputRef: state.searchInputRef,
goBack: state.backPage,
};
},
);
return (
<div className="h-14 flex items-center border-b border-border">
<motion.div
animate={{ opacity: !page ? 0 : 1, width: !page ? 0 : 130 }}
transition={{ ease: "easeOut" }}
className="overflow-hidden"
>
<AnimatePresence>
{page && (
<div className="shrink-0 flex items-center">
{/* @ts-ignore */}
<button
className="flex items-center shrink-0 text-icon gap-2 px-4"
onClick={goBack}
>
<ArrowLeftIcon className="size-4 " />
Go back
</button>
<motion.span
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: 24 }}
exit={{ opacity: 0, height: 0 }}
transition={{ delay: 0.3 }}
className="w-[1.5px] bg-white/30 shrink-0"
/>
</div>
)}
</AnimatePresence>
</motion.div>
<CommandInput
autoFocus
ref={searchInputRef}
value={search}
onValueChange={setSearch}
placeholder="Search for tools or simply ask anything..."
className="w-full h-full bg-transparent outline-none px-4 placeholder:text-placeholder text-[15px]"
/>
</div>
);
}
export default CommandSearch;

View file

@ -0,0 +1,21 @@
import { Cog6ToothIcon } from "@heroicons/react/16/solid";
function Footer() {
return (
<div className=" flex items-center justify-between">
{/* <Link href={'/'} className="text-branding flex items-center gap-2.5">
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" clipRule="evenodd" d="M1.61539 0C0.723233 0 0 0.723233 0 1.61539V12.3846C0 13.2768 0.723233 14 1.61539 14H12.3846C13.2768 14 14 13.2768 14 12.3846V1.61539C14 0.723233 13.2768 0 12.3846 0H1.61539ZM6.46153 7.5384C5.86676 7.5384 5.3846 8.0206 5.3846 8.61533V11.3077C5.3846 11.9024 5.86676 12.3845 6.46153 12.3845H11.3077C11.9025 12.3845 12.3846 11.9024 12.3846 11.3077V8.61533C12.3846 8.02053 11.9025 7.5384 11.3077 7.5384H6.46153Z" fill="#A3A4A5" />
</svg>
<p className="pt-0.5">Powered by Computir</p>
</Link> */}
<button className="text-icon flex items-center gap-2">
<Cog6ToothIcon className="size-4" />
Settings
</button>
</div>
);
}
export default Footer;

View file

@ -0,0 +1,16 @@
import React from "react";
const GroupLabel = React.forwardRef<
HTMLParagraphElement,
React.ComponentPropsWithoutRef<"p">
>(({ className, ...props }, ref) => (
<p
ref={ref}
className="text-xs font-medium text-label px-2 pb-3"
{...props}
/>
));
GroupLabel.displayName = "GroupLabel";
export { GroupLabel };

View file

@ -0,0 +1,20 @@
import React from "react";
import { Command } from "cmdk";
import cn from "mxcn";
const CommandItem = React.forwardRef<
React.ElementRef<typeof Command.Item>,
React.ComponentPropsWithoutRef<typeof Command.Item>
>(({ className, ...props }, ref) => (
<Command.Item
ref={ref}
className={cn(
`h-10 px-3 rounded-xl flex items-center gap-3 aria-selected:bg-focus duration-200 hover:cursor-pointer`,
className,
)}
{...props}
/>
));
CommandItem.displayName = "CommandItem";
export default CommandItem;

View file

@ -21,6 +21,7 @@
"private": true,
"version": "0.0.0",
"dependencies": {
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-select": "^2.1.1",
@ -28,8 +29,11 @@
"@radix-ui/react-tooltip": "^1.1.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"lucide-react": "^0.400.0",
"mxcn": "^2.0.0",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"zustand": "^4.5.4"
}
}

File diff suppressed because it is too large Load diff

24
pnpm-lock.yaml generated
View file

@ -307,6 +307,9 @@ importers:
apps/extension:
dependencies:
'@radix-ui/react-dialog':
specifier: ^1.0.5
version: 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
'@radix-ui/react-label':
specifier: ^2.1.0
version: 2.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
@ -328,15 +331,24 @@ importers:
clsx:
specifier: ^2.1.1
version: 2.1.1
cmdk:
specifier: ^1.0.0
version: 1.0.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1)(react@18.3.1)
lucide-react:
specifier: ^0.400.0
version: 0.400.0(react@18.3.1)
mxcn:
specifier: ^2.0.0
version: 2.0.0
tailwind-merge:
specifier: ^2.3.0
version: 2.4.0
tailwindcss-animate:
specifier: ^1.0.7
version: 1.0.7(tailwindcss@3.4.7)
zustand:
specifier: ^4.5.4
version: 4.5.4(@types/react@18.3.3)(react@18.3.1)
devDependencies:
'@extension-create/develop':
specifier: ^1.8.0
@ -18937,6 +18949,14 @@ packages:
resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
dev: true
/mxcn@2.0.0:
resolution: {integrity: sha512-v7HUffIn60+mllPxuqTzsZxAixpabEwzs/X45InZWOnT+ylJQ55fLADiwudEmqUhqH37PU3OT8mU755XIKgNgg==}
engines: {node: '>=12'}
dependencies:
clsx: 2.1.1
tailwind-merge: 1.14.0
dev: false
/mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
dependencies:
@ -23362,6 +23382,10 @@ packages:
strip-ansi: 6.0.1
dev: true
/tailwind-merge@1.14.0:
resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==}
dev: false
/tailwind-merge@2.4.0:
resolution: {integrity: sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A==}
dev: false