aboutsummaryrefslogtreecommitdiff
path: root/apps/web/app/(editor)/components/aigenerate.tsx
diff options
context:
space:
mode:
authorDhravya Shah <[email protected]>2024-06-18 17:58:46 -0500
committerDhravya Shah <[email protected]>2024-06-18 17:58:46 -0500
commitf4bb71e8f7e07bb2e919b7f222d5acb2905eb8f2 (patch)
tree7310dc521ef3559055bbe71f50c3861be2fa0503 /apps/web/app/(editor)/components/aigenerate.tsx
parentdarkmode by default - so that the colors don't f up on lightmode devices (diff)
parentCreate Embeddings for Canvas (diff)
downloadsupermemory-default-darkmode.tar.xz
supermemory-default-darkmode.zip
Diffstat (limited to 'apps/web/app/(editor)/components/aigenerate.tsx')
-rw-r--r--apps/web/app/(editor)/components/aigenerate.tsx169
1 files changed, 169 insertions, 0 deletions
diff --git a/apps/web/app/(editor)/components/aigenerate.tsx b/apps/web/app/(editor)/components/aigenerate.tsx
new file mode 100644
index 00000000..f27fd50f
--- /dev/null
+++ b/apps/web/app/(editor)/components/aigenerate.tsx
@@ -0,0 +1,169 @@
+import React, { useEffect, useRef, useState } from "react";
+import Magic from "./ui/magic";
+import CrazySpinner from "./ui/crazy-spinner";
+import Asksvg from "./ui/asksvg";
+import Rewritesvg from "./ui/rewritesvg";
+import Translatesvg from "./ui/translatesvg";
+import Autocompletesvg from "./ui/autocompletesvg";
+import { motion, AnimatePresence } from "framer-motion";
+import type { Editor } from "@tiptap/core";
+import { useEditor } from "novel";
+
+function Aigenerate() {
+ const [visible, setVisible] = useState(false);
+ const [generating, setGenerating] = useState(false);
+
+ const { editor } = useEditor();
+ const setGeneratingfn = (v: boolean) => setGenerating(v);
+
+ return (
+ <div className="z-[60] bg-[#171B1F] fixed left-0 bottom-0 w-screen flex justify-center pt-4 pb-6">
+ <motion.div
+ animate={{
+ y: visible ? "30%" : 0,
+ }}
+ onClick={() => {
+ setVisible(!visible);
+ if (visible) editor?.commands.unsetAIHighlight();
+ }}
+ className={`select-none relative z-[70] rounded-3xl text-[#369DFD] bg-[#21303D] px-4 py-3 text-sm flex gap-2 items-center font-medium whitespace-nowrap overflow-hidden transition-[width] w-[6.25rem] ${visible && "w-[10.55rem]"}`}
+ >
+ <Magic className="h-4 w-4 shrink-0 translate-y-[5%]" />
+ {visible && generating ? (
+ <>
+ Generating <CrazySpinner />
+ </>
+ ) : visible ? (
+ <>Press Commands</>
+ ) : (
+ <>Ask AI</>
+ )}
+ </motion.div>
+ <motion.div
+ initial={{
+ opacity: 0,
+ y: 20,
+ }}
+ animate={{
+ y: visible ? "-60%" : 20,
+ opacity: visible ? 1 : 0,
+ }}
+ whileHover={{ scale: 1.05 }}
+ transition={{
+ duration: 0.2,
+ }}
+ className="absolute z-50 top-0"
+ >
+ <ToolBar setGeneratingfn={setGeneratingfn} editor={editor} />
+ <div className="h-8 w-18rem bg-blue-600 blur-[16rem]" />
+ </motion.div>
+ </div>
+ );
+}
+
+export default Aigenerate;
+
+const options = [
+ <><Translatesvg />Translate</>,
+ <><Rewritesvg />Change Tone</>,
+ <><Asksvg />Ask Gemini</>,
+ <><Autocompletesvg />Auto Complete</>
+];
+
+function ToolBar({
+ editor,
+ setGeneratingfn,
+}: {
+ editor: Editor;
+ setGeneratingfn: (v: boolean) => void;
+}) {
+ const [index, setIndex] = useState(0);
+
+ return (
+ <div
+ className={
+ "select-none flex gap-6 bg-[#1F2428] active:scale-[.98] transition rounded-3xl px-1 py-1 text-sm font-medium"
+ }
+ >
+ {options.map((item, idx) => (
+ <div
+ key={idx}
+ className="relative block h-full w-full px-3 py-2 text-[#989EA4]"
+ onMouseEnter={() => setIndex(idx)}
+ >
+ <AnimatePresence>
+ {index === idx && (
+ <motion.span
+ onClick={() =>
+ AigenerateContent({ idx, editor, setGeneratingfn })
+ }
+ className="absolute select-none inset-0 block h-full w-full rounded-xl bg-background-light"
+ layoutId="hoverBackground"
+ initial={{ opacity: 0 }}
+ animate={{
+ opacity: 1,
+ transition: { duration: 0.15 },
+ }}
+ exit={{
+ opacity: 0,
+ transition: { duration: 0.15, delay: 0.2 },
+ }}
+ />
+ )}
+ </AnimatePresence>
+ <div className="select-none flex items-center whitespace-nowrap gap-3 relative z-[60] pointer-events-none">
+ {item}
+ </div>
+ </div>
+ ))}
+ </div>
+ );
+}
+
+async function AigenerateContent({
+ idx,
+ editor,
+ setGeneratingfn,
+}: {
+ idx: number;
+ editor: Editor;
+ setGeneratingfn: (v: boolean) => void;
+}) {
+ setGeneratingfn(true);
+
+ const { from, to } = editor.view.state.selection;
+
+ const slice = editor.state.selection.content();
+ const text = editor.storage.markdown.serializer.serialize(slice.content);
+
+ const request = [
+ "Translate to hindi written in english, do not write anything else",
+ "change tone, improve the way be more formal",
+ "ask, answer the question",
+ "continue this, maximum 30 characters, do not repeat just continue don't use ... to denote start",
+ ]
+
+ const res = await fetch("/api/editorai", {
+ method: "POST",
+ body: JSON.stringify({
+ context: text,
+ request: request[idx],
+ }),
+ })
+ const {completion}: {completion: string} = await res.json();
+ console.log(completion)
+
+ if (idx === 0 || idx === 1){
+ const selectionLength = completion.length + from
+ editor.chain().focus()
+ .insertContentAt({from, to}, completion).setTextSelection({from, to: selectionLength})
+ .run();
+ } else {
+ const selectionLength = completion.length + to + 1
+ editor.chain().focus()
+ .insertContentAt(to+1, completion).setTextSelection({from, to: selectionLength})
+ .run();
+ }
+
+ setGeneratingfn(false);
+}