diff options
| author | codetorso <[email protected]> | 2024-07-20 07:17:25 +0530 |
|---|---|---|
| committer | codetorso <[email protected]> | 2024-07-20 07:17:25 +0530 |
| commit | 21fe55a96c36892fc50e8198248da825a5a4bd6f (patch) | |
| tree | 83832e20adb0e4acfa529ac624e7f2b914f7a25a /apps/web/app/(dash)/chat | |
| parent | fix links (diff) | |
| download | supermemory-torso.tar.xz supermemory-torso.zip | |
for god's sake this should work ;)torso
Diffstat (limited to 'apps/web/app/(dash)/chat')
| -rw-r--r-- | apps/web/app/(dash)/chat/chatWindow.tsx | 218 | ||||
| -rw-r--r-- | apps/web/app/(dash)/chat/queryinput.tsx | 83 |
2 files changed, 200 insertions, 101 deletions
diff --git a/apps/web/app/(dash)/chat/chatWindow.tsx b/apps/web/app/(dash)/chat/chatWindow.tsx index f0827a3d..066e7d20 100644 --- a/apps/web/app/(dash)/chat/chatWindow.tsx +++ b/apps/web/app/(dash)/chat/chatWindow.tsx @@ -2,7 +2,7 @@ import { AnimatePresence } from "framer-motion"; import React, { useEffect, useRef, useState } from "react"; -import QueryInput from "../home/queryinput"; +import QueryInput from "./queryinput"; import { cn } from "@repo/ui/lib/utils"; import { motion } from "framer-motion"; import { useRouter } from "next/navigation"; @@ -224,9 +224,73 @@ function ChatWindow({ {chat.question} </h2> - <div className="flex flex-col mt-2"> + <div className="flex flex-col"> + {/* Related memories */} + <div + className={`space-y-4 ${chat.answer.sources.length > 0 || chat.answer.parts.length === 0 ? "flex" : "hidden"}`} + > + <Accordion + defaultValue={ + idx === chatHistory.length - 1 ? "memories" : "" + } + type="single" + collapsible + > + <AccordionItem value="memories"> + <AccordionTrigger className="text-foreground-menu"> + Related Memories + </AccordionTrigger> + {/* TODO: fade out content on the right side, the fade goes away when the user scrolls */} + <AccordionContent + className="flex items-center no-scrollbar overflow-auto gap-4 relative max-w-3xl no-scrollbar" + defaultChecked + > + {/* Loading state */} + {chat.answer.sources.length > 0 || + (chat.answer.parts.length === 0 && ( + <> + {[1, 2, 3, 4].map((_, idx) => ( + <div + key={`loadingState-${idx}`} + className="w-[350px] shrink-0 p-4 gap-2 rounded-2xl flex flex-col bg-secondary animate-pulse" + > + <div className="bg-slate-700 h-2 rounded-full w-1/2"></div> + <div className="bg-slate-700 h-2 rounded-full w-full"></div> + </div> + ))} + </> + ))} + {chat.answer.sources.map((source, idx) => ( + <Link + href={source.source} + key={idx} + className="w-[350px] shrink-0 p-4 gap-2 rounded-2xl flex flex-col bg-secondary" + > + <div className="flex justify-between text-foreground-menu text-sm"> + <span>{source.type}</span> + + {source.numChunks > 1 && ( + <span>{source.numChunks} chunks</span> + )} + </div> + <div className="text-base"> + {source.title} + </div> + <div className="text-xs line-clamp-2"> + {source.content.length > 100 + ? source.content.slice(0, 100) + "..." + : source.content} + </div> + </Link> + ))} + </AccordionContent> + </AccordionItem> + </Accordion> + </div> + + {/* Summary */} <div> - <div className="text-foreground-menu py-2">Answer</div> + <div className="text-foreground-menu py-2">Summary</div> <div className="text-base"> {/* Loading state */} {(chat.answer.parts.length === 0 || @@ -283,108 +347,60 @@ function ChatWindow({ > <ClipboardIcon className="size-[18px] group-hover:text-primary" /> </button> + <button + onClick={async () => { + const isWebShareSupported = + navigator.share !== undefined; + if (isWebShareSupported) { + try { + await navigator.share({ + title: "Your Share Title", + text: "Your share text or description", + url: "https://your-url-to-share.com", + }); + } catch (e) { + console.error("Error sharing:", e); + } + } else { + console.error("web share is not supported!"); + } + }} + className="group h-8 w-8 flex justify-center items-center active:scale-75 duration-200" + > + <SendIcon className="size-[18px] group-hover:text-primary" /> + </button> </div> </div> </div> - - <div - className={`space-y-4 ${chat.answer.sources.length > 0 || chat.answer.parts.length === 0 ? "flex" : "hidden"}`} - > - <Accordion - defaultValue={ - idx === chatHistory.length - 1 ? "memories" : "" - } - type="single" - collapsible - > - <AccordionItem value="memories"> - <AccordionTrigger className="text-foreground-menu"> - Related Memories - </AccordionTrigger> - {/* TODO: fade out content on the right side, the fade goes away when the user scrolls */} - <AccordionContent - className="flex flex-col no-scrollbar overflow-auto gap-4 relative max-w-3xl no-scrollbar" - defaultChecked + {/* Justification */} + {chat.answer.justification && + chat.answer.justification.length && ( + <div + className={`${chat.answer.justification && chat.answer.justification.length > 0 ? "flex" : "hidden"}`} + > + <Accordion + defaultValue={""} + type="single" + collapsible > - <div className="w-full no-scrollbar flex gap-4"> - {/* Loading state */} - {chat.answer.sources.length > 0 || - (chat.answer.parts.length === 0 && ( - <> - {[1, 2, 3, 4].map((_, idx) => ( - <div - key={`loadingState-${idx}`} - className="w-[350px] shrink-0 p-4 gap-2 rounded-2xl flex flex-col bg-secondary animate-pulse" - > - <div className="bg-slate-700 h-2 rounded-full w-1/2"></div> - <div className="bg-slate-700 h-2 rounded-full w-full"></div> - </div> - ))} - </> - ))} - {chat.answer.sources.map((source, idx) => ( - <Link - href={source.source} - key={idx} - className="w-[350px] shrink-0 p-4 gap-2 rounded-2xl flex flex-col bg-secondary" - > - <div className="flex justify-between text-foreground-menu text-sm"> - <span>{source.type}</span> - - {source.numChunks > 1 && ( - <span>{source.numChunks} chunks</span> - )} - </div> - <div className="text-base"> - {source.title} - </div> - <div className="text-xs line-clamp-2"> - {source.content.length > 100 - ? source.content.slice(0, 100) + "..." - : source.content} - </div> - </Link> - ))} - </div> - - {chat.answer.justification && - chat.answer.justification.length && ( - <div - className={`${chat.answer.justification && chat.answer.justification.length > 0 ? "flex" : "hidden"}`} - > - <Accordion - defaultValue={""} - type="single" - collapsible - > - <AccordionItem value="justification"> - <AccordionTrigger className="text-foreground-menu"> - Justification - </AccordionTrigger> - <AccordionContent - className="relative flex gap-2 max-w-3xl overflow-auto no-scrollbar" - defaultChecked - > - {chat.answer.justification.length > 0 - ? chat.answer.justification - .replaceAll( - "<justification>", - "", - ) - .replaceAll( - "</justification>", - "", - ) - : "No justification provided."} - </AccordionContent> - </AccordionItem> - </Accordion> - </div> - )} - </AccordionContent> - </AccordionItem> - </Accordion> - </div> + <AccordionItem value="justification"> + <AccordionTrigger className="text-foreground-menu"> + Justification + </AccordionTrigger> + <AccordionContent + className="relative flex gap-2 max-w-3xl overflow-auto no-scrollbar" + defaultChecked + > + {chat.answer.justification.length > 0 + ? chat.answer.justification + .replaceAll("<justification>", "") + .replaceAll("</justification>", "") + : "No justification provided."} + </AccordionContent> + </AccordionItem> + </Accordion> + </div> + )} </div> </div> </div> diff --git a/apps/web/app/(dash)/chat/queryinput.tsx b/apps/web/app/(dash)/chat/queryinput.tsx new file mode 100644 index 00000000..99f55986 --- /dev/null +++ b/apps/web/app/(dash)/chat/queryinput.tsx @@ -0,0 +1,83 @@ +"use client"; + +import { ArrowRightIcon } from "@repo/ui/icons"; +import Image from "next/image"; +import React, { useState } from "react"; + +function QueryInput({ + initialSpaces, + initialQuery = "", + disabled = false, + className, + mini = false, + handleSubmit, +}: { + initialQuery?: string; + initialSpaces?: { + id: number; + name: string; + }[]; + disabled?: boolean; + className?: string; + mini?: boolean; + handleSubmit: (q: string, spaces: { id: number; name: string }[]) => void; +}) { + const [q, setQ] = useState(initialQuery); + + const [selectedSpaces, setSelectedSpaces] = useState< + { id: number; name: string }[] + >([]); + + return ( + <div className={`${className}`}> + <div + className={`bg-[#1F2428] overflow-hidden border-2 border-gray-700/50 shadow-md shadow-[#1d1d1dc7] rounded-3xl`} + > + {/* input and action button */} + <form + action={async () => { + if (q.trim().length === 0) { + return; + } + handleSubmit(q, selectedSpaces); + setQ(""); + }} + className="flex gap-4 p-3" + > + <textarea + autoFocus + name="q" + cols={30} + rows={mini ? 2 : 4} + className="bg-transparent pt-2.5 text-lg placeholder:text-[#9B9B9B] focus:text-gray-200 duration-200 tracking-[3%] outline-none resize-none w-full p-4" + placeholder="Ask your second brain..." + onKeyDown={(e) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + if (q.trim().length === 0) { + return; + } + handleSubmit(q, selectedSpaces); + setQ(""); + } + }} + onChange={(e) => setQ(e.target.value)} + value={q} + disabled={disabled} + /> + + <button + type="submit" + disabled={disabled} + className="h-12 w-12 rounded-[14px] bg-border all-center shrink-0 hover:brightness-125 duration-200 outline-none focus:outline focus:outline-primary active:scale-90" + > + <Image src={ArrowRightIcon} alt="Right arrow icon" /> + </button> + </form>{" "} + </div> + {/* selected sources */} + </div> + ); +} + +export default QueryInput; |