aboutsummaryrefslogtreecommitdiff
path: root/apps/web/app/(dash)/chat/chatQueryInput.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web/app/(dash)/chat/chatQueryInput.tsx')
-rw-r--r--apps/web/app/(dash)/chat/chatQueryInput.tsx181
1 files changed, 181 insertions, 0 deletions
diff --git a/apps/web/app/(dash)/chat/chatQueryInput.tsx b/apps/web/app/(dash)/chat/chatQueryInput.tsx
new file mode 100644
index 00000000..c7267298
--- /dev/null
+++ b/apps/web/app/(dash)/chat/chatQueryInput.tsx
@@ -0,0 +1,181 @@
+"use client";
+
+import { ArrowRightIcon } from "@repo/ui/icons";
+import Image from "next/image";
+import React, { useEffect, useMemo, useState } from "react";
+import Divider from "@repo/ui/shadcn/divider";
+import { useRouter } from "next/navigation";
+import { getSpaces } from "@/app/actions/fetchers";
+import Combobox from "@repo/ui/shadcn/combobox";
+import { MinusIcon } from "lucide-react";
+import { toast } from "sonner";
+import { createSpace } from "@/app/actions/doers";
+
+function QueryInput({
+ initialQuery = "",
+ initialSpaces = [],
+ disabled = false,
+ className,
+ mini = false,
+ handleSubmit,
+ setInitialSpaces,
+}: {
+ initialQuery?: string;
+ initialSpaces?: {
+ id: number;
+ name: string;
+ }[];
+ disabled?: boolean;
+ className?: string;
+ mini?: boolean;
+ handleSubmit: (q: string, spaces: { id: number; name: string }[]) => void;
+ setInitialSpaces?: React.Dispatch<
+ React.SetStateAction<{ id: number; name: string }[]>
+ >;
+}) {
+ const [q, setQ] = useState(initialQuery);
+
+ const [selectedSpaces, setSelectedSpaces] = useState<number[]>([]);
+
+ const options = useMemo(
+ () =>
+ initialSpaces.map((x) => ({
+ label: x.name,
+ value: x.id.toString(),
+ })),
+ [initialSpaces],
+ );
+
+ const preparedSpaces = useMemo(
+ () =>
+ initialSpaces
+ .filter((x) => selectedSpaces.includes(x.id))
+ .map((x) => {
+ return {
+ id: x.id,
+ name: x.name,
+ };
+ }),
+ [selectedSpaces, initialSpaces],
+ );
+
+ return (
+ <div className={`${className}`}>
+ <div
+ className={`bg-secondary border-2 border-b-0 border-border ${!mini ? "rounded-t-3xl" : "rounded-3xl"}`}
+ >
+ {/* input and action button */}
+ <form
+ action={async () => {
+ handleSubmit(q, preparedSpaces);
+ setQ("");
+ }}
+ className="flex gap-4 p-3"
+ >
+ <textarea
+ autoFocus
+ name="q"
+ cols={30}
+ rows={mini ? 2 : 4}
+ className="bg-transparent pt-2.5 text-base 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, preparedSpaces);
+ setQ("");
+ }
+ }}
+ onChange={(e) => setQ(e.target.value)}
+ value={q}
+ disabled={disabled}
+ />
+
+ <button
+ type="submit"
+ onClick={(e) => {
+ e.preventDefault();
+ if (q.trim().length === 0) {
+ return;
+ }
+ handleSubmit(q, preparedSpaces);
+ }}
+ 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 */}
+ {!mini && (
+ <>
+ <Divider />
+ <div className="flex justify-between items-center gap-6 h-auto bg-secondary rounded-b-3xl border-2 border-border">
+ <Combobox
+ options={options}
+ className="rounded-bl-3xl bg-[#3C464D] w-44"
+ onSelect={(v) =>
+ setSelectedSpaces((prev) => {
+ if (v === "") {
+ return [];
+ }
+ return [...prev, parseInt(v)];
+ })
+ }
+ onSubmit={async (spaceName) => {
+ const space = options.find((x) => x.label === spaceName);
+ toast.info("Creating space...");
+
+ if (space) {
+ toast.error("A space with that name already exists.");
+ }
+
+ const creationTask = await createSpace(spaceName);
+ if (creationTask.success && creationTask.data) {
+ toast.success("Space created " + creationTask.data);
+ setInitialSpaces?.((prev) => [
+ ...prev,
+ {
+ name: spaceName,
+ id: creationTask.data!,
+ },
+ ]);
+ setSelectedSpaces((prev) => [...prev, creationTask.data!]);
+ } else {
+ toast.error(
+ "Space creation failed: " + creationTask.error ??
+ "Unknown error",
+ );
+ }
+ }}
+ placeholder="Chat with a space..."
+ />
+
+ <div className="flex flex-row gap-0.5 h-full">
+ {preparedSpaces.map((x, idx) => (
+ <button
+ key={x.id}
+ onClick={() =>
+ setSelectedSpaces((prev) => prev.filter((y) => y !== x.id))
+ }
+ className={`relative group p-2 py-3 bg-[#3C464D] max-w-32 ${idx === preparedSpaces.length - 1 ? "rounded-br-xl" : ""}`}
+ >
+ <p className="line-clamp-1">{x.name}</p>
+ <div className="absolute h-full right-0 top-0 p-1 opacity-0 group-hover:opacity-100 items-center">
+ <MinusIcon className="w-6 h-6 rounded-full bg-secondary" />
+ </div>
+ </button>
+ ))}
+ </div>
+ </div>
+ </>
+ )}
+ </div>
+ );
+}
+
+export default QueryInput;