aboutsummaryrefslogtreecommitdiff
path: root/apps/web/app/(dash)/chat
diff options
context:
space:
mode:
authorcodetorso <[email protected]>2024-07-20 07:17:25 +0530
committercodetorso <[email protected]>2024-07-20 07:17:25 +0530
commit21fe55a96c36892fc50e8198248da825a5a4bd6f (patch)
tree83832e20adb0e4acfa529ac624e7f2b914f7a25a /apps/web/app/(dash)/chat
parentfix links (diff)
downloadsupermemory-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.tsx218
-rw-r--r--apps/web/app/(dash)/chat/queryinput.tsx83
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;