aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDhravya <[email protected]>2024-06-22 18:12:39 -0500
committerDhravya <[email protected]>2024-06-22 18:12:39 -0500
commit02d7f0834ecb50b7f26479710aa361b113cbbd24 (patch)
treefffb2b5a9b9a5bbad2b238f8e5cbca9343d77be3
parentaddeed chathistory functionality (diff)
parentdelete packagelock (diff)
downloadsupermemory-02d7f0834ecb50b7f26479710aa361b113cbbd24.tar.xz
supermemory-02d7f0834ecb50b7f26479710aa361b113cbbd24.zip
merged code
-rw-r--r--apps/web/app/(canvas)/canvas.css3
-rw-r--r--apps/web/app/(canvas)/canvas.tsx32
-rw-r--r--apps/web/app/(canvas)/canvas/page.tsx168
-rw-r--r--apps/web/app/(canvas)/dropComponent.tsx153
-rw-r--r--apps/web/app/(canvas)/lib/createEmbeds.ts322
-rw-r--r--apps/web/app/(canvas)/lib/loadSnap.ts9
-rw-r--r--apps/web/app/(canvas)/textCard.tsx76
-rw-r--r--apps/web/app/(dash)/chat/chatWindow.tsx325
-rw-r--r--apps/web/app/(dash)/chat/page.tsx104
-rw-r--r--apps/web/app/(dash)/dynamicisland.tsx18
-rw-r--r--apps/web/app/(dash)/header.tsx18
-rw-r--r--apps/web/app/(dash)/home/page.tsx2
-rw-r--r--apps/web/app/(dash)/home/queryinput.tsx3
-rw-r--r--apps/web/app/(dash)/layout.tsx8
-rw-r--r--apps/web/app/(dash)/menu.tsx17
-rw-r--r--apps/web/app/(editor)/ai.md43
-rw-r--r--apps/web/app/(editor)/components/aigenerate.tsx186
-rw-r--r--apps/web/app/(editor)/components/editorcommands.tsx85
-rw-r--r--apps/web/app/(editor)/components/extensions.ts141
-rw-r--r--apps/web/app/(editor)/components/image-upload.ts50
-rw-r--r--apps/web/app/(editor)/components/selectors/bgcolor-selector.tsx107
-rw-r--r--apps/web/app/(editor)/components/selectors/color-selector.tsx111
-rw-r--r--apps/web/app/(editor)/components/selectors/link-selector.tsx95
-rw-r--r--apps/web/app/(editor)/components/selectors/node-selector.tsx126
-rw-r--r--apps/web/app/(editor)/components/selectors/text-buttons.tsx63
-rw-r--r--apps/web/app/(editor)/components/slash-command.tsx163
-rw-r--r--apps/web/app/(editor)/components/topbar.tsx68
-rw-r--r--apps/web/app/(editor)/components/ui/asksvg.tsx12
-rw-r--r--apps/web/app/(editor)/components/ui/autocompletesvg.tsx12
-rw-r--r--apps/web/app/(editor)/components/ui/crazy-spinner.tsx11
-rw-r--r--apps/web/app/(editor)/components/ui/magic.tsx8
-rw-r--r--apps/web/app/(editor)/components/ui/rewritesvg.tsx11
-rw-r--r--apps/web/app/(editor)/components/ui/translatesvg.tsx12
-rw-r--r--apps/web/app/(editor)/editor.tsx55
-rw-r--r--apps/web/app/(editor)/editor/page.tsx8
-rw-r--r--apps/web/app/(editor)/layout.tsx12
-rw-r--r--apps/web/app/(editor)/lib/content.ts231
-rw-r--r--apps/web/app/(editor)/lib/debouncedsave.ts20
-rw-r--r--apps/web/app/(editor)/lib/editorprops.ts16
-rw-r--r--apps/web/app/(editor)/lib/use-local-storage.ts27
-rw-r--r--apps/web/app/(editor)/styles/globals.css168
-rw-r--r--apps/web/app/(editor)/styles/prosemirror.css203
-rw-r--r--apps/web/public/logo.svg2
-rw-r--r--package.json3
-rw-r--r--packages/tailwind-config/globals.css16
-rw-r--r--packages/ui/components/QueryInput.tsx60
-rw-r--r--packages/ui/components/canvas/draggableComponent.tsx71
-rw-r--r--packages/ui/icons/add.svg2
-rw-r--r--packages/ui/icons/autocomplete.svg15
-rw-r--r--packages/ui/icons/block.svg15
-rw-r--r--packages/ui/icons/canvas.svg3
-rw-r--r--packages/ui/icons/chat.svg2
-rw-r--r--packages/ui/icons/drag.svg (renamed from apps/web/app/(canvas)/svg.tsx)85
-rw-r--r--packages/ui/icons/explore.svg2
-rw-r--r--packages/ui/icons/history.svg2
-rw-r--r--packages/ui/icons/index.ts16
-rw-r--r--packages/ui/icons/link.svg15
-rw-r--r--packages/ui/icons/memories.svg2
-rw-r--r--packages/ui/icons/settings.svg14
-rw-r--r--packages/ui/shadcn/accordion.tsx5
-rw-r--r--packages/ui/shadcn/switch.tsx29
61 files changed, 1125 insertions, 2536 deletions
diff --git a/apps/web/app/(canvas)/canvas.css b/apps/web/app/(canvas)/canvas.css
new file mode 100644
index 00000000..3e6700da
--- /dev/null
+++ b/apps/web/app/(canvas)/canvas.css
@@ -0,0 +1,3 @@
+.tlui-dialog__overlay {
+ position: fixed;
+}
diff --git a/apps/web/app/(canvas)/canvas.tsx b/apps/web/app/(canvas)/canvas.tsx
index 498ab1eb..aaf89a06 100644
--- a/apps/web/app/(canvas)/canvas.tsx
+++ b/apps/web/app/(canvas)/canvas.tsx
@@ -4,6 +4,7 @@ import { createAssetFromUrl } from "./lib/createAssetUrl";
import "tldraw/tldraw.css";
import { components } from "./enabledComp";
import { twitterCardUtil } from "./twitterCard";
+import { textCardUtil } from "./textCard";
import createEmbedsFromUrl from "./lib/createEmbeds";
import { loadRemoteSnapshot } from "./lib/loadSnap";
import { SaveStatus } from "./savesnap";
@@ -11,49 +12,40 @@ import { getAssetUrls } from "@tldraw/assets/selfHosted";
import { memo } from "react";
import DragContext from "./lib/context";
import DropZone from "./dropComponent";
+import "./canvas.css";
export const Canvas = memo(() => {
const [isDraggingOver, setIsDraggingOver] = useState<boolean>(false);
- const Dragref = useRef<HTMLDivElement | null>(null)
+ const Dragref = useRef<HTMLDivElement | null>(null);
const handleDragOver = (event: any) => {
event.preventDefault();
setIsDraggingOver(true);
- console.log("entere")
- };
-
- const handleDragLeave = () => {
- setIsDraggingOver(false);
- console.log("leaver")
+ console.log("entere");
};
useEffect(() => {
const divElement = Dragref.current;
if (divElement) {
- divElement.addEventListener('dragover', handleDragOver);
- divElement.addEventListener('dragleave', handleDragLeave);
+ divElement.addEventListener("dragover", handleDragOver);
}
return () => {
if (divElement) {
- divElement.removeEventListener('dragover', handleDragOver);
- divElement.removeEventListener('dragleave', handleDragLeave);
+ divElement.removeEventListener("dragover", handleDragOver);
}
};
}, []);
return (
<DragContext.Provider value={{ isDraggingOver, setIsDraggingOver }}>
- <div
- ref={Dragref}
- className="w-full h-full"
- >
- <TldrawComponent />
- </div>
+ <div ref={Dragref} className="w-full h-full">
+ <TldrawComponent />
+ </div>
</DragContext.Provider>
);
});
-const TldrawComponent =memo(() => {
+const TldrawComponent = memo(() => {
const [storeWithStatus, setStoreWithStatus] = useState<TLStoreWithStatus>({
status: "loading",
});
@@ -89,7 +81,7 @@ const TldrawComponent =memo(() => {
assetUrls={assetUrls}
components={components}
store={storeWithStatus}
- shapeUtils={[twitterCardUtil]}
+ shapeUtils={[twitterCardUtil, textCardUtil]}
onMount={handleMount}
>
<div className="absolute left-1/2 top-0 z-[1000000] flex -translate-x-1/2 gap-2 bg-[#2C3439] text-[#B3BCC5]">
@@ -99,4 +91,4 @@ const TldrawComponent =memo(() => {
</Tldraw>
</div>
);
-})
+});
diff --git a/apps/web/app/(canvas)/canvas/page.tsx b/apps/web/app/(canvas)/canvas/page.tsx
index 366a4481..533ed33c 100644
--- a/apps/web/app/(canvas)/canvas/page.tsx
+++ b/apps/web/app/(canvas)/canvas/page.tsx
@@ -1,92 +1,70 @@
"use client";
-// import Canvas from "./_components/canvas";
-import {Canvas} from "../canvas";
+import { Canvas } from "../canvas";
import React, { useState } from "react";
-// import ReactTextareaAutosize from "react-textarea-autosize";
import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
-import {
- DragSvg,
- SettingsSvg,
- LinkSvg,
- ThreeDBlock,
- TextLoadingSvg,
-} from "../svg";
+import { SettingsIcon, DragIcon } from "@repo/ui/icons";
+import DraggableComponentsContainer from "@repo/ui/components/canvas/draggableComponent";
+import { AutocompleteIcon, blockIcon } from "@repo/ui/icons";
+import Image from "next/image";
+import { Switch } from "@repo/ui/shadcn/switch";
+import { Label } from "@repo/ui/shadcn/label";
function page() {
- const [value, setValue] = useState("");
const [fullScreen, setFullScreen] = useState(false);
+ const [visible, setVisible] = useState(true);
return (
- <div className={`h-screen w-full ${ !fullScreen ? "px-4 py-6": "bg-[#1F2428]"} transition-all`}>
+ <div
+ className={`h-screen w-full ${!fullScreen ? "px-4 py-6" : "bg-[#1F2428]"} transition-all`}
+ >
<div>
- <PanelGroup className={` ${fullScreen ? "w-[calc(100vw-2rem)]" : "w-screen"} transition-all`} direction="horizontal">
- <Panel onExpand={()=> {setTimeout(()=> setFullScreen(false), 50)}} onCollapse={()=> {setTimeout(()=> setFullScreen(true), 50)}} defaultSize={30} collapsible={true} minSize={22}>
- <div className={`flex transition-all rounded-2xl ${fullScreen ? "h-screen": "h-[calc(100vh-3rem)]"} w-full flex-col overflow-hidden bg-[#1F2428]`}>
+ <PanelGroup
+ onLayout={(l) => {
+ l[0]! < 20 ? setVisible(false) : setVisible(true);
+ }}
+ className={` ${fullScreen ? "w-[calc(100vw-2rem)]" : "w-screen"} transition-all`}
+ direction="horizontal"
+ >
+ <Panel
+ onExpand={() => {
+ setTimeout(() => setFullScreen(false), 50);
+ }}
+ onCollapse={() => {
+ setTimeout(() => setFullScreen(true), 50);
+ }}
+ defaultSize={30}
+ collapsible={true}
+ >
+ <div
+ className={`flex transition-all rounded-2xl ${fullScreen ? "h-screen" : "h-[calc(100vh-3rem)]"} w-full flex-col overflow-hidden bg-[#1F2428]`}
+ >
<div className="flex items-center justify-between bg-[#2C3439] px-4 py-2 text-lg font-medium text-[#989EA4]">
Change Filters
- <SettingsSvg />
- </div>
- <div className="px-3 py-5">
- <input
- placeholder="search..."
- onChange={(e) => {
- setValue(e.target.value);
- }}
- value={value}
- // rows={1}
- className="w-full resize-none rounded-xl bg-[#151515] px-3 py-4 text-xl text-[#989EA4] outline-none focus:outline-none sm:max-h-52"
- />
- </div>
- <div className="flex flex-col gap-10">
- <div className="flex gap-4 px-3 text-[#989EA4]">
- <TextLoadingSvg />
- <h1>
- Nvidia will most likely create monopoly in software industry
- as they are already largest player in GPU hardware by 20...
- </h1>
- </div>
- <div className="flex gap-4 px-3 text-[#989EA4]">
- <ThreeDBlock />
- <div className="flex flex-col gap-2">
- <div>
- <h1 className="line-clamp-3">
- Nvidia currently dominates the GPU hardware market, with
- a market share over 97%. This has led some to argue...
- </h1>
- </div>
- <p className="line-clamp-1 text-[#369DFD]">
- From space: GPU GOATS
- </p>
- </div>
- </div>
- <div className="flex gap-4 px-3 text-[#989EA4]">
- <LinkSvg />
- <div className="flex flex-col gap-2">
- <div>
- <h1 className="line-clamp-3">
- Nvidia currently dominates the GPU hardware market, with
- a market share over 97%. This has led some to argue...
- </h1>
- </div>
- <p className="line-clamp-1 text-[#369DFD]">
- Page url:
- https://www.cnbc.com/2024/05/23/nvidia-keeps-hitting-records-can-investors-still-buy-the-stock.html?&qsearchterm=nvidia
- </p>
- </div>
- </div>
+ <Image src={SettingsIcon} alt="setting-icon" />
</div>
+ {visible ? (
+ <SidePanel />
+ ) : (
+ <h1 className="text-center py-10 text-xl">
+ Need more space to show!
+ </h1>
+ )}
</div>
</Panel>
- <PanelResizeHandle className={`relative flex items-center transition-all justify-center ${!fullScreen && "px-1"}`}>
- {/* <div className="absolute z-[1000000] top-1/2 -translate-y-1/2"> */}
- <div className={`rounded-lg bg-[#2F363B] ${!fullScreen && "px-1"} transition-all py-2`}>
- <DragSvg />
+ <PanelResizeHandle
+ className={`relative flex items-center transition-all justify-center ${!fullScreen && "px-1"}`}
+ >
+ <div
+ className={`rounded-lg bg-[#2F363B] ${!fullScreen && "px-1"} transition-all py-2`}
+ >
+ <Image src={DragIcon} alt="drag-icon" />
</div>
- {/* </div> */}
</PanelResizeHandle>
<Panel className="relative" defaultSize={70} minSize={60}>
- <div className={`absolute overflow-hidden transition-all inset-0 ${ fullScreen ? "h-screen " : "h-[calc(100vh-3rem)] rounded-2xl"} w-full`}>
+ <div
+ className={`absolute overflow-hidden transition-all inset-0 ${fullScreen ? "h-screen " : "h-[calc(100vh-3rem)] rounded-2xl"} w-full`}
+ >
<Canvas />
</div>
</Panel>
@@ -96,4 +74,52 @@ function page() {
);
}
+function SidePanel() {
+ const [value, setValue] = useState("");
+ const [dragAsText, setDragAsText] = useState(false);
+ return (
+ <>
+ <div className="px-3 py-5">
+ <input
+ placeholder="search..."
+ onChange={(e) => {
+ setValue(e.target.value);
+ }}
+ value={value}
+ // rows={1}
+ className="w-full resize-none rounded-xl bg-[#151515] px-3 py-4 text-xl text-[#989EA4] outline-none focus:outline-none sm:max-h-52"
+ />
+ </div>
+ <div className="flex items-center justify-end px-3 py-4">
+ <Switch
+ className="bg-[#151515] data-[state=unchecked]:bg-red-400 data-[state=checked]:bg-blue-400"
+ onCheckedChange={(e) => setDragAsText(e)}
+ id="drag-text-mode"
+ />
+ <Label htmlFor="drag-text-mode">Drag as Text</Label>
+ </div>
+ <DraggableComponentsContainer content={content} />
+ </>
+ );
+}
+
export default page;
+
+const content = [
+ {
+ content:
+ "Regional growth patterns diverge, with strong performance in the United States and several emerging markets, contrasted by weaker prospects in many advanced economies, particularly in Europe (World Economic Forum) (OECD). The rapid adoption of artificial intelligence (AI) is expected to drive productivity growth, especially in advanced economies, potentially mitigating labor shortages and boosting income levels in emerging markets (World Economic Forum) (OECD). However, ongoing geopolitical tensions and economic fragmentation are likely to maintain a level of uncertainty and volatility in the global economy (World Economic Forum.",
+ icon: AutocompleteIcon,
+ iconAlt: "Autocomplete",
+ extraInfo:
+ "Page Url: https://chatgpt.com/c/762cd44e-1752-495b-967a-aa3c23c6024a",
+ },
+ {
+ content:
+ "As of mid-2024, the global economy is experiencing modest growth with significant regional disparities. Global GDP growth is projected to be around 3.1% in 2024, rising slightly to 3.2% in 2025. This performance, although below the pre-pandemic average, reflects resilience despite various economic pressures, including tight monetary conditions and geopolitical tensions (IMF)(OECD) Inflation is moderating faster than expected, with global headline inflation projected to fall to 5.8% in 2024 and 4.4% in 2025, contributing to improving real incomes and positive trade growth (IMF) (OECD)",
+ icon: blockIcon,
+ iconAlt: "Autocomplete",
+ extraInfo:
+ "Page Url: https://www.cnbc.com/2024/05/23/nvidia-keeps-hitting-records-can-investors-still-buy-the-stock.html?&qsearchterm=nvidia",
+ },
+];
diff --git a/apps/web/app/(canvas)/dropComponent.tsx b/apps/web/app/(canvas)/dropComponent.tsx
index 03a32358..a14bd1f3 100644
--- a/apps/web/app/(canvas)/dropComponent.tsx
+++ b/apps/web/app/(canvas)/dropComponent.tsx
@@ -9,25 +9,65 @@ const stripHtmlTags = (html: string): string => {
return div.textContent || div.innerText || "";
};
+function formatTextToRatio(text: string) {
+ const totalWidth = text.length;
+ const maxLineWidth = Math.floor(totalWidth / 4);
+
+ const words = text.split(" ");
+ let lines = [];
+ let currentLine = "";
+
+ words.forEach((word) => {
+ // Check if adding the next word exceeds the maximum line width
+ if ((currentLine + word).length <= maxLineWidth) {
+ currentLine += (currentLine ? " " : "") + word;
+ } else {
+ // If the current line is full, push it to new line
+ lines.push(currentLine);
+ currentLine = word;
+ }
+ });
+ if (currentLine) {
+ lines.push(currentLine);
+ }
+ return lines.join("\n");
+}
+
const useDrag = (): DragContextType => {
const context = useContext(DragContext);
if (!context) {
- throw new Error('useCounter must be used within a CounterProvider');
+ throw new Error("useCounter must be used within a CounterProvider");
}
return context;
};
-
function DropZone() {
const dropRef = useRef<HTMLDivElement | null>(null);
- const {isDraggingOver, setIsDraggingOver} = useDrag();
+ const { isDraggingOver, setIsDraggingOver } = useDrag();
const editor = useEditor();
- const handleDrop = useCallback((event: React.DragEvent<HTMLDivElement>) => {
+ const handleDragLeave = () => {
+ setIsDraggingOver(false);
+ console.log("leaver");
+ };
+
+ useEffect(() => {
+ setInterval(() => {
+ editor.selectAll();
+ const shapes = editor.getSelectedShapes();
+ const text = shapes.filter((s) => s.type === "text");
+ console.log("hrhh", text);
+ }, 5000);
+ }, []);
+
+ const handleDrop = useCallback((event: DragEvent) => {
event.preventDefault();
setIsDraggingOver(false);
const dt = event.dataTransfer;
+ if (!dt) {
+ return;
+ }
const items = dt.items;
for (let i = 0; i < items.length; i++) {
@@ -45,7 +85,8 @@ function DropZone() {
} else if (items[i]!.kind === "string") {
items[i]!.getAsString((data) => {
const cleanText = stripHtmlTags(data);
- handleExternalDroppedContent({editor,text:cleanText})
+ const onethree = formatTextToRatio(cleanText);
+ handleExternalDroppedContent({ editor, text: onethree });
});
}
}
@@ -54,22 +95,116 @@ function DropZone() {
useEffect(() => {
const divElement = dropRef.current;
if (divElement) {
- // @ts-ignore
divElement.addEventListener("drop", handleDrop);
+ divElement.addEventListener("dragleave", handleDragLeave);
}
return () => {
if (divElement) {
- // @ts-ignore
divElement.removeEventListener("drop", handleDrop);
+ divElement.addEventListener("dragleave", handleDragLeave);
}
};
}, []);
return (
<div
- className={`h-full w-full absolute top-0 left-0 z-[100000] pointer-events-none ${isDraggingOver && "bg-[#2C3439] pointer-events-auto"}`}
+ className={`h-full flex justify-center items-center w-full absolute top-0 left-0 z-[100000] pointer-events-none ${isDraggingOver && "bg-[#2c3439ad] pointer-events-auto"}`}
ref={dropRef}
- ></div>
+ >
+ {isDraggingOver && (
+ <>
+ <div className="absolute top-4 left-8">
+ <TopRight />
+ </div>
+ <div className="absolute top-4 right-8">
+ <TopLeft />
+ </div>
+ <div className="absolute bottom-4 left-8">
+ <BottomLeft />
+ </div>
+ <div className="absolute bottom-4 right-8">
+ <BottomRight />
+ </div>
+ <h2 className="text-2xl">Drop here to add Content on Canvas</h2>
+ </>
+ )}
+ </div>
+ );
+}
+
+function TopRight() {
+ return (
+ <svg
+ width="48"
+ height="48"
+ viewBox="0 0 48 48"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M44 4H12C7.58172 4 4 7.58172 4 12V44"
+ stroke="white"
+ stroke-width="8"
+ stroke-linecap="round"
+ />
+ </svg>
+ );
+}
+
+function TopLeft() {
+ return (
+ <svg
+ width="48"
+ height="48"
+ viewBox="0 0 48 48"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M4 4H36C40.4183 4 44 7.58172 44 12V44"
+ stroke="white"
+ stroke-width="8"
+ stroke-linecap="round"
+ />
+ </svg>
+ );
+}
+
+function BottomLeft() {
+ return (
+ <svg
+ width="48"
+ height="48"
+ viewBox="0 0 48 48"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M44 44H12C7.58172 44 4 40.4183 4 36V4"
+ stroke="white"
+ stroke-width="8"
+ stroke-linecap="round"
+ />
+ </svg>
+ );
+}
+
+function BottomRight() {
+ return (
+ <svg
+ width="48"
+ height="48"
+ viewBox="0 0 48 48"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M4 44H36C40.4183 44 44 40.4183 44 36V4"
+ stroke="white"
+ stroke-width="8"
+ stroke-linecap="round"
+ />
+ </svg>
);
}
diff --git a/apps/web/app/(canvas)/lib/createEmbeds.ts b/apps/web/app/(canvas)/lib/createEmbeds.ts
index 0db3c71b..64eb0627 100644
--- a/apps/web/app/(canvas)/lib/createEmbeds.ts
+++ b/apps/web/app/(canvas)/lib/createEmbeds.ts
@@ -1,17 +1,34 @@
-import { AssetRecordType, Editor, TLAsset, TLAssetId, TLBookmarkShape, TLExternalContentSource, TLShapePartial, Vec, VecLike, createShapeId, getEmbedInfo, getHashForString } from "tldraw";
-
-export default async function createEmbedsFromUrl({url, point, sources, editor}: {
- url: string
- point?: VecLike | undefined
- sources?: TLExternalContentSource[] | undefined
- editor: Editor
-}){
-
+import {
+ AssetRecordType,
+ Editor,
+ TLAsset,
+ TLAssetId,
+ TLBookmarkShape,
+ TLExternalContentSource,
+ TLShapePartial,
+ Vec,
+ VecLike,
+ createShapeId,
+ getEmbedInfo,
+ getHashForString,
+} from "tldraw";
+
+export default async function createEmbedsFromUrl({
+ url,
+ point,
+ sources,
+ editor,
+}: {
+ url: string;
+ point?: VecLike | undefined;
+ sources?: TLExternalContentSource[] | undefined;
+ editor: Editor;
+}) {
const position =
- point ??
- (editor.inputs.shiftKey
- ? editor.inputs.currentPagePoint
- : editor.getViewportPageBounds().center);
+ point ??
+ (editor.inputs.shiftKey
+ ? editor.inputs.currentPagePoint
+ : editor.getViewportPageBounds().center);
if (url?.includes("x.com") || url?.includes("twitter.com")) {
return editor.createShape({
@@ -20,71 +37,71 @@ export default async function createEmbedsFromUrl({url, point, sources, editor}:
y: position.y - 150,
props: { url: url },
});
-
}
- // try to paste as an embed first
- const embedInfo = getEmbedInfo(url);
+ // try to paste as an embed first
+ const embedInfo = getEmbedInfo(url);
- if (embedInfo) {
- return editor.putExternalContent({
- type: "embed",
- url: embedInfo.url,
- point,
- embed: embedInfo.definition,
- });
- }
+ if (embedInfo) {
+ return editor.putExternalContent({
+ type: "embed",
+ url: embedInfo.url,
+ point,
+ embed: embedInfo.definition,
+ });
+ }
- const assetId: TLAssetId = AssetRecordType.createId(
- getHashForString(url),
- );
- const shape = createEmptyBookmarkShape(editor, url, position);
-
- // Use an existing asset if we have one, or else else create a new one
- let asset = editor.getAsset(assetId) as TLAsset;
- let shouldAlsoCreateAsset = false;
- if (!asset) {
- shouldAlsoCreateAsset = true;
- try {
- const bookmarkAsset = await editor.getAssetForExternalContent({
- type: "url",
- url,
- });
- const fetchWebsite: {
- title?: string;
- image?: string;
- description?: string;
- } = await (await fetch(`/api/unfirlsite?website=${url}`, {
- method: "POST"
- })).json()
- if (bookmarkAsset){
- if (fetchWebsite.title) bookmarkAsset.props.title = fetchWebsite.title;
- if (fetchWebsite.image) bookmarkAsset.props.image = fetchWebsite.image;
- if (fetchWebsite.description) bookmarkAsset.props.description = fetchWebsite.description;
- }
- if (!bookmarkAsset) throw Error("Could not create an asset");
- asset = bookmarkAsset;
- } catch (e) {
- console.log(e)
- return;
+ const assetId: TLAssetId = AssetRecordType.createId(getHashForString(url));
+ const shape = createEmptyBookmarkShape(editor, url, position);
+
+ // Use an existing asset if we have one, or else else create a new one
+ let asset = editor.getAsset(assetId) as TLAsset;
+ let shouldAlsoCreateAsset = false;
+ if (!asset) {
+ shouldAlsoCreateAsset = true;
+ try {
+ const bookmarkAsset = await editor.getAssetForExternalContent({
+ type: "url",
+ url,
+ });
+ const fetchWebsite: {
+ title?: string;
+ image?: string;
+ description?: string;
+ } = await (
+ await fetch(`/api/unfirlsite?website=${url}`, {
+ method: "POST",
+ })
+ ).json();
+ if (bookmarkAsset) {
+ if (fetchWebsite.title) bookmarkAsset.props.title = fetchWebsite.title;
+ if (fetchWebsite.image) bookmarkAsset.props.image = fetchWebsite.image;
+ if (fetchWebsite.description)
+ bookmarkAsset.props.description = fetchWebsite.description;
}
+ if (!bookmarkAsset) throw Error("Could not create an asset");
+ asset = bookmarkAsset;
+ } catch (e) {
+ console.log(e);
+ return;
}
+ }
- editor.batch(() => {
- if (shouldAlsoCreateAsset) {
- editor.createAssets([asset]);
- }
+ editor.batch(() => {
+ if (shouldAlsoCreateAsset) {
+ editor.createAssets([asset]);
+ }
- editor.updateShapes([
- {
- id: shape.id,
- type: shape.type,
- props: {
- assetId: asset.id,
- },
+ editor.updateShapes([
+ {
+ id: shape.id,
+ type: shape.type,
+ props: {
+ assetId: asset.id,
},
- ]);
- });
+ },
+ ]);
+ });
}
function isURL(str: string) {
@@ -96,79 +113,122 @@ function isURL(str: string) {
}
}
+function formatTextToRatio(text: string) {
+ const totalWidth = text.length;
+ const maxLineWidth = Math.floor(totalWidth / 10);
-export function handleExternalDroppedContent({text, editor}: {text:string, editor: Editor}){
- const position = editor.inputs.shiftKey
- ? editor.inputs.currentPagePoint
- : editor.getViewportPageBounds().center;
+ const words = text.split(" ");
+ let lines = [];
+ let currentLine = "";
+
+ words.forEach((word) => {
+ if ((currentLine + word).length <= maxLineWidth) {
+ currentLine += (currentLine ? " " : "") + word;
+ } else {
+ lines.push(currentLine);
+ currentLine = word;
+ }
+ });
+ if (currentLine) {
+ lines.push(currentLine);
+ }
+ return { height: (lines.length + 1) * 18, width: maxLineWidth * 10 };
+}
- if (isURL(text)){
- createEmbedsFromUrl({editor, url: text})
- } else{
+export function handleExternalDroppedContent({
+ text,
+ editor,
+}: {
+ text: string;
+ editor: Editor;
+}) {
+ const position = editor.inputs.shiftKey
+ ? editor.inputs.currentPagePoint
+ : editor.getViewportPageBounds().center;
+
+ if (isURL(text)) {
+ createEmbedsFromUrl({ editor, url: text });
+ } else {
+ // editor.createShape({
+ // type: "text",
+ // x: position.x - 75,
+ // y: position.y - 75,
+ // props: {
+ // text: text,
+ // size: "s",
+ // textAlign: "start",
+ // },
+ // });
+ const { height, width } = formatTextToRatio(text);
editor.createShape({
- type: "text",
- x: position.x - 75,
- y: position.y - 75,
+ type: "Textcard",
+ x: position.x - width / 2,
+ y: position.y - height / 2,
props: {
- text: text,
- size: "s",
- textAlign: "start",
+ content: text,
+ extrainfo: "https://chatgpt.com/c/762cd44e-1752-495b-967a-aa3c23c6024a",
+ w: width,
+ h: height,
},
});
-
}
}
function centerSelectionAroundPoint(editor: Editor, position: VecLike) {
- // Re-position shapes so that the center of the group is at the provided point
- const viewportPageBounds = editor.getViewportPageBounds()
- let selectionPageBounds = editor.getSelectionPageBounds()
-
- if (selectionPageBounds) {
- const offset = selectionPageBounds!.center.sub(position)
-
- editor.updateShapes(
- editor.getSelectedShapes().map((shape) => {
- const localRotation = editor.getShapeParentTransform(shape).decompose().rotation
- const localDelta = Vec.Rot(offset, -localRotation)
- return {
- id: shape.id,
- type: shape.type,
- x: shape.x! - localDelta.x,
- y: shape.y! - localDelta.y,
- }
- })
- )
- }
-
- // Zoom out to fit the shapes, if necessary
- selectionPageBounds = editor.getSelectionPageBounds()
- if (selectionPageBounds && !viewportPageBounds.contains(selectionPageBounds)) {
- editor.zoomToSelection()
- }
+ // Re-position shapes so that the center of the group is at the provided point
+ const viewportPageBounds = editor.getViewportPageBounds();
+ let selectionPageBounds = editor.getSelectionPageBounds();
+
+ if (selectionPageBounds) {
+ const offset = selectionPageBounds!.center.sub(position);
+
+ editor.updateShapes(
+ editor.getSelectedShapes().map((shape) => {
+ const localRotation = editor
+ .getShapeParentTransform(shape)
+ .decompose().rotation;
+ const localDelta = Vec.Rot(offset, -localRotation);
+ return {
+ id: shape.id,
+ type: shape.type,
+ x: shape.x! - localDelta.x,
+ y: shape.y! - localDelta.y,
+ };
+ }),
+ );
+ }
+
+ // Zoom out to fit the shapes, if necessary
+ selectionPageBounds = editor.getSelectionPageBounds();
+ if (
+ selectionPageBounds &&
+ !viewportPageBounds.contains(selectionPageBounds)
+ ) {
+ editor.zoomToSelection();
+ }
}
export function createEmptyBookmarkShape(
- editor: Editor,
- url: string,
- position: VecLike
+ editor: Editor,
+ url: string,
+ position: VecLike,
): TLBookmarkShape {
- const partial: TLShapePartial = {
- id: createShapeId(),
- type: 'bookmark',
- x: position.x - 150,
- y: position.y - 160,
- opacity: 1,
- props: {
- assetId: null,
- url,
- },
- }
-
- editor.batch(() => {
- editor.createShapes([partial]).select(partial.id)
- centerSelectionAroundPoint(editor, position)
- })
-
- return editor.getShape(partial.id) as TLBookmarkShape
-} \ No newline at end of file
+ const partial: TLShapePartial = {
+ id: createShapeId(),
+ type: "bookmark",
+ x: position.x - 150,
+ y: position.y - 160,
+ opacity: 1,
+ props: {
+ assetId: null,
+ url,
+ },
+ };
+
+ editor.batch(() => {
+ editor.createShapes([partial]).select(partial.id);
+ centerSelectionAroundPoint(editor, position);
+ });
+
+ return editor.getShape(partial.id) as TLBookmarkShape;
+}
diff --git a/apps/web/app/(canvas)/lib/loadSnap.ts b/apps/web/app/(canvas)/lib/loadSnap.ts
index 15aad998..c6b748a9 100644
--- a/apps/web/app/(canvas)/lib/loadSnap.ts
+++ b/apps/web/app/(canvas)/lib/loadSnap.ts
@@ -1,13 +1,14 @@
-import { createTLStore, defaultShapeUtils } from "tldraw";
+import { createTLStore, defaultShapeUtils, loadSnapshot } from "tldraw";
import { twitterCardUtil } from "../twitterCard";
+import { textCardUtil } from "../textCard";
export async function loadRemoteSnapshot() {
const res = await fetch(
"https://learning-cf.pruthvirajthinks.workers.dev/get/page3",
);
const snapshot = JSON.parse(await res.json());
const newStore = createTLStore({
- shapeUtils: [...defaultShapeUtils, twitterCardUtil],
+ shapeUtils: [...defaultShapeUtils, twitterCardUtil, textCardUtil],
});
- newStore.loadSnapshot(snapshot);
+ loadSnapshot(newStore, snapshot);
return newStore;
-} \ No newline at end of file
+}
diff --git a/apps/web/app/(canvas)/textCard.tsx b/apps/web/app/(canvas)/textCard.tsx
new file mode 100644
index 00000000..065c5ae1
--- /dev/null
+++ b/apps/web/app/(canvas)/textCard.tsx
@@ -0,0 +1,76 @@
+import {
+ BaseBoxShapeUtil,
+ HTMLContainer,
+ TLBaseBoxShape,
+ TLBaseShape,
+ useIsEditing,
+ useValue,
+} from "tldraw";
+
+type ITextCardShape = TLBaseShape<
+ "Textcard",
+ { w: number; h: number; content: string; extrainfo: string }
+>;
+
+export class textCardUtil extends BaseBoxShapeUtil<ITextCardShape> {
+ static override type = "Textcard" as const;
+
+ getDefaultProps(): ITextCardShape["props"] {
+ return {
+ w: 100,
+ h: 50,
+ content: "",
+ extrainfo: "",
+ };
+ }
+
+ component(s: ITextCardShape) {
+ const isEditing = useIsEditing(s.id);
+ const isHoveringWhileEditingSameShape = useValue(
+ "is hovering",
+ () => {
+ const { editingShapeId, hoveredShapeId } =
+ this.editor.getCurrentPageState();
+
+ if (editingShapeId && hoveredShapeId !== editingShapeId) {
+ const editingShape = this.editor.getShape(editingShapeId);
+ if (
+ editingShape &&
+ this.editor.isShapeOfType<TLBaseBoxShape>(editingShape, "embed")
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+ [],
+ );
+
+ const isInteractive = isEditing || isHoveringWhileEditingSameShape;
+ return (
+ <HTMLContainer className="flex h-full w-full items-center justify-center">
+ <div
+ style={{
+ height: s.props.h,
+ width: s.props.w,
+ pointerEvents: isInteractive ? "all" : "none",
+ zIndex: isInteractive ? "" : "-1",
+ background: "#2C3439",
+ borderRadius: "16px",
+ padding: "8px 14px",
+ }}
+ >
+ <h1 style={{ fontSize: "15px" }}>{s.props.content}</h1>
+ <p style={{ fontSize: "14px", color: "#369DFD" }}>
+ {s.props.extrainfo}
+ </p>
+ </div>
+ </HTMLContainer>
+ );
+ }
+
+ indicator(shape: ITextCardShape) {
+ return <rect width={shape.props.w} height={shape.props.h} />;
+ }
+}
diff --git a/apps/web/app/(dash)/chat/chatWindow.tsx b/apps/web/app/(dash)/chat/chatWindow.tsx
index 97530f60..2be1d6c1 100644
--- a/apps/web/app/(dash)/chat/chatWindow.tsx
+++ b/apps/web/app/(dash)/chat/chatWindow.tsx
@@ -24,6 +24,12 @@ import { z } from "zod";
import { toast } from "sonner";
import Link from "next/link";
import { createChatObject } from "@/app/actions/doers";
+import {
+ ClipboardIcon,
+ ShareIcon,
+ SpeakerWaveIcon,
+} from "@heroicons/react/24/outline";
+import { SendIcon } from "lucide-react";
function ChatWindow({
q,
@@ -48,7 +54,6 @@ function ChatWindow({
initialChat.length > 1 ? "chat" : "initial",
);
const [chatHistory, setChatHistory] = useState<ChatHistory[]>(initialChat);
- const [isAutoScroll, setIsAutoScroll] = useState(true);
const removeJustificationFromText = (text: string) => {
// remove everything after the first "<justification>" word
@@ -164,12 +169,10 @@ function ChatWindow({
const lastAnswer = newChatHistory[newChatHistory.length - 1];
if (!lastAnswer) return prevChatHistory;
- if (isAutoScroll) {
- window.scrollTo({
- top: document.documentElement.scrollHeight,
- behavior: "smooth",
- });
- }
+ window.scrollTo({
+ top: document.documentElement.scrollHeight,
+ behavior: "smooth",
+ });
lastAnswer.answer.parts.push({ text: txt });
return newChatHistory;
@@ -218,151 +221,207 @@ function ChatWindow({
</motion.div>
) : (
<div
- className="max-w-3xl relative flex mx-auto w-full flex-col mt-24 pb-32"
+ className="max-w-3xl z-10 mx-auto relative h-full overflow-y-auto no-scrollbar"
key="chat"
>
- {chatHistory.map((chat, idx) => (
- <div
- key={idx}
- className={`mt-8 ${idx != chatHistory.length - 1 ? "pb-2 border-b border-b-gray-400" : ""}`}
- >
- <h2
- className={cn(
- "text-white transition-all transform translate-y-0 opacity-100 duration-500 ease-in-out font-semibold text-2xl",
- )}
- >
- {chat.question}
- </h2>
-
- <div className="flex flex-col gap-2 mt-2">
+ <div className="w-full pt-24 mb-40">
+ {chatHistory.map((chat, idx) => (
+ <div key={idx} className="space-y-16">
<div
- className={`${chat.answer.sources.length > 0 || chat.answer.parts.length === 0 ? "flex" : "hidden"}`}
+ className={`mt-8 ${idx != chatHistory.length - 1 ? "pb-2 border-b border-b-gray-400" : ""}`}
>
- <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="relative flex gap-2 max-w-3xl overflow-auto 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="rounded-xl bg-secondary p-4 flex flex-col gap-2 min-w-72 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="rounded-xl bg-secondary p-4 flex flex-col gap-2 min-w-72"
- >
- <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">
- {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">Summary</div>
- <div className="text-base">
- {chat.answer.parts.length === 0 && (
- <div className="animate-pulse flex space-x-4">
- <div className="flex-1 space-y-3 py-1">
- <div className="h-2 bg-slate-700 rounded"></div>
- <div className="h-2 bg-slate-700 rounded"></div>
- </div>
- </div>
+ <h2
+ className={cn(
+ "text-white transition-all transform translate-y-0 opacity-100 duration-500 ease-in-out font-semibold text-xl",
)}
- <Markdown
- remarkPlugins={[remarkGfm, [remarkMath]]}
- rehypePlugins={[
- rehypeKatex,
- [
- rehypeHighlight,
- {
- detect: true,
- ignoreMissing: true,
- subset: codeLanguageSubset,
- },
- ],
- ]}
- components={{
- code: code as any,
- p: p as any,
- }}
- className="flex flex-col gap-2"
- >
- {removeJustificationFromText(
- chat.answer.parts.map((part) => part.text).join(""),
- )}
- </Markdown>
- </div>
- </div>
+ >
+ {chat.question}
+ </h2>
- {/* Justification */}
- {chat.answer.justification &&
- chat.answer.justification.length && (
+ <div className="flex flex-col">
+ {/* Related memories */}
<div
- className={`${chat.answer.justification && chat.answer.justification.length > 0 ? "flex" : "hidden"}`}
+ className={`space-y-4 ${chat.answer.sources.length > 0 || chat.answer.parts.length === 0 ? "flex" : "hidden"}`}
>
- <Accordion defaultValue={""} type="single" collapsible>
- <AccordionItem value="justification">
+ <Accordion
+ defaultValue={
+ idx === chatHistory.length - 1 ? "memories" : ""
+ }
+ type="single"
+ collapsible
+ >
+ <AccordionItem value="memories">
<AccordionTrigger className="text-foreground-menu">
- Justification
+ Related Memories
</AccordionTrigger>
+ {/* TODO: fade out content on the right side, the fade goes away when the user scrolls */}
<AccordionContent
- className="relative flex gap-2 max-w-3xl overflow-auto no-scrollbar"
+ className="flex items-center no-scrollbar overflow-auto gap-4 relative max-w-3xl no-scrollbar"
defaultChecked
>
- {chat.answer.justification.length > 0
- ? chat.answer.justification
- .replaceAll("<justification>", "")
- .replaceAll("</justification>", "")
- : "No justification provided."}
+ {/* 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">Summary</div>
+ <div className="text-base">
+ {/* Loading state */}
+ {(chat.answer.parts.length === 0 ||
+ chat.answer.parts.join("").length === 0) && (
+ <div className="animate-pulse flex space-x-4">
+ <div className="flex-1 space-y-3 py-1">
+ <div className="h-2 bg-slate-700 rounded"></div>
+ <div className="h-2 bg-slate-700 rounded"></div>
+ </div>
+ </div>
+ )}
+
+ <Markdown
+ remarkPlugins={[remarkGfm, [remarkMath]]}
+ rehypePlugins={[
+ rehypeKatex,
+ [
+ rehypeHighlight,
+ {
+ detect: true,
+ ignoreMissing: true,
+ subset: codeLanguageSubset,
+ },
+ ],
+ ]}
+ components={{
+ code: code as any,
+ p: p as any,
+ }}
+ className="flex flex-col gap-2 text-base"
+ >
+ {removeJustificationFromText(
+ chat.answer.parts
+ .map((part) => part.text)
+ .join(""),
+ )}
+ </Markdown>
+
+ <div className="mt-3 relative -left-2 flex items-center gap-1">
+ {/* TODO: speak response */}
+ {/* <button className="group h-8 w-8 flex justify-center items-center active:scale-75 duration-200">
+ <SpeakerWaveIcon className="size-[18px] group-hover:text-primary" />
+ </button> */}
+ {/* copy response */}
+ <button
+ onClick={() =>
+ navigator.clipboard.writeText(
+ chat.answer.parts
+ .map((part) => part.text)
+ .join(""),
+ )
+ }
+ className="group h-8 w-8 flex justify-center items-center active:scale-75 duration-200"
+ >
+ <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>
+ {/* 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
+ >
+ <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>
- </div>
- ))}
+ ))}
+ </div>
- <div className="fixed bottom-0 w-full max-w-3xl pb-4">
+ <div className="fixed bottom-4 w-full max-w-3xl">
<QueryInput
mini
className="w-full shadow-md"
diff --git a/apps/web/app/(dash)/chat/page.tsx b/apps/web/app/(dash)/chat/page.tsx
index 28ade147..1ce59ec5 100644
--- a/apps/web/app/(dash)/chat/page.tsx
+++ b/apps/web/app/(dash)/chat/page.tsx
@@ -1,5 +1,13 @@
import ChatWindow from "./chatWindow";
-import { chatSearchParamsCache } from "../../../lib/searchParams";
+import { chatSearchParamsCache } from "@/lib/searchParams";
+import {
+ ChevronDownIcon,
+ ClipboardIcon,
+ SpeakerWaveIcon,
+} from "@heroicons/react/24/outline";
+import Image from "next/image";
+import { ArrowRightIcon } from "@repo/ui/icons";
+import QueryInput from "@repo/ui/components/QueryInput";
// @ts-expect-error
await import("katex/dist/katex.min.css");
@@ -13,6 +21,100 @@ function Page({
console.log(spaces);
return <ChatWindow q={q} spaces={spaces} threadId={""} />;
+ return (
+ <div className="max-w-3xl z-10 mx-auto relative h-full overflow-y-auto no-scrollbar">
+ {/* <ChatWindow q={q} spaces={[]} /> */}
+
+ <div className="w-full pt-24 space-y-40">
+ {/* single q&A */}
+ {Array.from({ length: 1 }).map((_, i) => (
+ <div key={i} className="space-y-16">
+ {/* header */}
+ <div>
+ {/* query */}
+ <h1 className="text-white text-xl">
+ Why is Retrieval-Augmented Generation important?
+ </h1>
+ </div>
+
+ {/* response */}
+ <div className="space-y-10">
+ {/* related memories */}
+ <div className="space-y-4">
+ {/* section header */}
+ <div className="flex items-center gap-3">
+ <h1>Related memories</h1>
+ <button>
+ <ChevronDownIcon className="size-4 stroke-2" />
+ </button>
+ </div>
+
+ {/* section content */}
+ {/* collection of memories */}
+ <div className="flex items-center no-scrollbar overflow-auto gap-4">
+ {/* related memory */}
+ {Array.from({ length: 3 }).map((_, i) => (
+ <div
+ key={i}
+ className="w-[350px] shrink-0 p-4 gap-2 rounded-2xl flex flex-col bg-secondary"
+ >
+ <h3 className="text-[13px]">Webpage</h3>
+ <p className="line-clamp-2 text-white">
+ What is RAG? - Retrieval-Augmented Generation Explained
+ - AWS
+ </p>
+ </div>
+ ))}
+ </div>
+ </div>
+
+ {/* summary */}
+ <div className="space-y-4">
+ {/* section header */}
+ <div className="flex items-center gap-3">
+ <h1>Summary</h1>
+ <button>
+ <ChevronDownIcon className="size-4 stroke-2" />
+ </button>
+ </div>
+
+ {/* section content */}
+ <div>
+ <p className="text-white text-base">
+ Retrieval-Augmented Generation is crucial because it
+ combines the strengths of retrieval-based methods, ensuring
+ relevance and accuracy, with generation-based models,
+ enabling creativity and flexibility. By integrating
+ retrieval mechanisms, it addresses data sparsity issues,
+ improves content relevance, offers fine-tuned control over
+ output, handles ambiguity, and allows for continual
+ learning, making it highly adaptable and effective across
+ various natural language processing tasks and domains.
+ </p>
+
+ {/* response actions */}
+ <div className="mt-3 relative -left-2 flex items-center gap-1">
+ {/* speak response */}
+ <button className="group h-8 w-8 flex justify-center items-center active:scale-75 duration-200">
+ <SpeakerWaveIcon className="size-[18px] group-hover:text-primary" />
+ </button>
+ {/* copy response */}
+ <button className="group h-8 w-8 flex justify-center items-center active:scale-75 duration-200">
+ <ClipboardIcon className="size-[18px] group-hover:text-primary" />
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ ))}
+ </div>
+
+ <div className="fixed bottom-4 max-w-3xl w-full">
+ <QueryInput />
+ </div>
+ </div>
+ );
}
export default Page;
diff --git a/apps/web/app/(dash)/dynamicisland.tsx b/apps/web/app/(dash)/dynamicisland.tsx
index 6fa56fae..6d3965ca 100644
--- a/apps/web/app/(dash)/dynamicisland.tsx
+++ b/apps/web/app/(dash)/dynamicisland.tsx
@@ -39,7 +39,7 @@ export function DynamicIsland() {
});
return (
- <div className="fixed z-40 left-1/2 -translate-x-1/2 top-12">
+ <div className="">
<AnimatePresence mode="wait">
<motion.div
initial={{
@@ -90,19 +90,15 @@ function DynamicIslandContent() {
return (
<>
{show ? (
- <div
+ <button
onClick={() => setshow(!show)}
- className="bg-secondary px-3 w-[2.23rem] overflow-hidden hover:w-[9.2rem] whitespace-nowrap py-2 rounded-3xl transition-[width] cursor-pointer"
+ className="bg-secondary p-2 text-[#989EA4] rounded-full flex items-center justify-between gap-2 px-4 h-10 pr-5 z-[999] shadow-md"
>
- <div className="flex gap-4 items-center">
- <Image src={AddIcon} alt="Add icon" />
- Add Content
- </div>
- </div>
+ <Image src={AddIcon} alt="add icon" />
+ Add content
+ </button>
) : (
- <div>
- <ToolBar cancelfn={cancelfn} />
- </div>
+ <ToolBar cancelfn={cancelfn} />
)}
</>
);
diff --git a/apps/web/app/(dash)/header.tsx b/apps/web/app/(dash)/header.tsx
index 026cb080..91c00125 100644
--- a/apps/web/app/(dash)/header.tsx
+++ b/apps/web/app/(dash)/header.tsx
@@ -2,26 +2,28 @@ import React from "react";
import Image from "next/image";
import Link from "next/link";
import Logo from "../../public/logo.svg";
-import { ChatIcon } from "@repo/ui/icons";
+import { AddIcon, ChatIcon } from "@repo/ui/icons";
import DynamicIsland from "./dynamicisland";
function Header() {
return (
- <div>
- <div className="fixed left-0 w-full flex items-center justify-between z-10">
- <Link className="px-5" href="/home">
+ <div className="p-4 relative z-30 h-16 flex items-center">
+ <div className="w-full flex items-center justify-between">
+ <Link className="" href="/home">
<Image
src={Logo}
alt="SuperMemory logo"
- className="hover:brightness-75 brightness-50 duration-200"
+ className="hover:brightness-125 duration-200"
/>
</Link>
- <DynamicIsland />
+ <div className="fixed z-30 left-1/2 -translate-x-1/2 top-5">
+ <DynamicIsland />
+ </div>
- <button className="flex shrink-0 duration-200 items-center gap-2 px-5 py-1.5 rounded-xl hover:bg-secondary">
- <Image src={ChatIcon} alt="Chat icon" />
+ <button className="flex duration-200 items-center text-[#7D8994] hover:bg-[#1F2429] text-[13px] gap-2 px-3 py-2 rounded-xl">
+ <Image src={ChatIcon} alt="Chat icon" className="w-5" />
Start new chat
</button>
</div>
diff --git a/apps/web/app/(dash)/home/page.tsx b/apps/web/app/(dash)/home/page.tsx
index 7226de24..b6cfd223 100644
--- a/apps/web/app/(dash)/home/page.tsx
+++ b/apps/web/app/(dash)/home/page.tsx
@@ -33,7 +33,7 @@ function Page({
{/* all content goes here */}
{/* <div className="">hi {firstTime ? 'first time' : ''}</div> */}
- <div className="w-full h-96">
+ <div className="w-full pb-20">
<QueryInput
handleSubmit={async (q, spaces) => {
const threadid = await createChatThread(q);
diff --git a/apps/web/app/(dash)/home/queryinput.tsx b/apps/web/app/(dash)/home/queryinput.tsx
index ce45e36b..46038225 100644
--- a/apps/web/app/(dash)/home/queryinput.tsx
+++ b/apps/web/app/(dash)/home/queryinput.tsx
@@ -69,7 +69,7 @@ function QueryInput({
name="q"
cols={30}
rows={mini ? 2 : 4}
- className="bg-transparent pt-2.5 text-base text-[#989EA4] focus:text-foreground duration-200 tracking-[3%] outline-none resize-none w-full p-4"
+ className="bg-transparent pt-2.5 text-base placeholder:text-[#5D6165] text-[#9DA0A4] 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") {
@@ -85,6 +85,7 @@ function QueryInput({
<button
type="submit"
+ onClick={(e) => e.preventDefault()}
disabled={disabled}
className="h-12 w-12 rounded-[14px] bg-[#21303D] all-center shrink-0 hover:brightness-125 duration-200 outline-none focus:outline focus:outline-primary active:scale-90"
>
diff --git a/apps/web/app/(dash)/layout.tsx b/apps/web/app/(dash)/layout.tsx
index 3ec8926e..1666aa1c 100644
--- a/apps/web/app/(dash)/layout.tsx
+++ b/apps/web/app/(dash)/layout.tsx
@@ -12,12 +12,14 @@ async function Layout({ children }: { children: React.ReactNode }) {
}
return (
- <main className="h-screen flex flex-col p-4 relative ">
- <Header />
+ <main className="h-screen flex flex-col">
+ <div className="fixed top-0 left-0 w-full">
+ <Header />
+ </div>
<Menu />
- {children}
+ <div className="w-full h-full">{children}</div>
<Toaster />
</main>
diff --git a/apps/web/app/(dash)/menu.tsx b/apps/web/app/(dash)/menu.tsx
index 5f26f545..23fa2bef 100644
--- a/apps/web/app/(dash)/menu.tsx
+++ b/apps/web/app/(dash)/menu.tsx
@@ -1,6 +1,11 @@
import React from "react";
import Image from "next/image";
-import { MemoriesIcon, ExploreIcon, HistoryIcon } from "@repo/ui/icons";
+import {
+ MemoriesIcon,
+ ExploreIcon,
+ HistoryIcon,
+ CanvasIcon,
+} from "@repo/ui/icons";
import Link from "next/link";
function Menu() {
@@ -16,21 +21,21 @@ function Menu() {
url: "/explore",
},
{
- icon: HistoryIcon,
- text: "History",
- url: "/history",
+ icon: CanvasIcon,
+ text: "Canvas",
+ url: "/canvas",
},
];
return (
- <div className="fixed h-screen pb-[25vh] w-full p-4 flex items-end justify-end lg:justify-start lg:items-center top-0 left-0 pointer-events-none">
+ <div className="fixed h-screen pb-20 w-full p-4 flex items-end justify-end lg:justify-start lg:items-center top-0 left-0 pointer-events-none">
<div className="">
<div className="pointer-events-auto group flex w-14 text-foreground-menu text-[15px] font-medium flex-col items-start gap-6 overflow-hidden rounded-[28px] bg-secondary px-3 py-4 duration-200 hover:w-40">
{menuItems.map((item) => (
<Link
href={item.url}
key={item.url}
- className="flex w-full cursor-pointer items-center gap-3 px-1 duration-200 hover:scale-105 hover:brightness-150 active:scale-90 justify-end md:justify-start"
+ className="flex w-full text-[#777E87] brightness-75 hover:brightness-125 cursor-pointer items-center gap-3 px-1 duration-200 hover:scale-105 active:scale-90 justify-end md:justify-start"
>
<p className="md:hidden opacity-0 duration-200 group-hover:opacity-100">
{item.text}
diff --git a/apps/web/app/(editor)/ai.md b/apps/web/app/(editor)/ai.md
deleted file mode 100644
index 1528a7bd..00000000
--- a/apps/web/app/(editor)/ai.md
+++ /dev/null
@@ -1,43 +0,0 @@
-## to access the editor
-```
-import { useEditor } from "novel";
-const editor = useEditor()
-```
-
-## to get previous text
-```
-import { getPrevText } from "novel/utils";
-const pos = editor.state.selection.from;
-const text = getPrevText(editor, pos);
-```
-
-## selected content into markdown format
-```
-const slice = editor.state.selection.content();
-const text = editor.storage.markdown.serializer.serialize(slice.content);
-```
-
-## replace Selection
-```
-const selection = editor.view.state.selection;
-editor.chain().focus()
- .insertContentAt(
- {
- from: selection.from,
- to: selection.to,
- },
- completion,
- )
- .run();
-```
-
-
-## to insert after
-```
-const selection = editor.view.state.selection;
-editor
- .chain()
- .focus()
- .insertContentAt(selection.to + 1, completion)
- .run();
-```
diff --git a/apps/web/app/(editor)/components/aigenerate.tsx b/apps/web/app/(editor)/components/aigenerate.tsx
deleted file mode 100644
index de9b2a3f..00000000
--- a/apps/web/app/(editor)/components/aigenerate.tsx
+++ /dev/null
@@ -1,186 +0,0 @@
-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";
-import { NodeSelection } from "prosemirror-state";
-
-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"
- >
- {/* TODO: handle Editor not initalised, maybe with a loading state. */}
- <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-[#33393D]"
- 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, minimum 80 characters, do not repeat just continue don't use ... to denote start",
- ];
-
- const resp = await fetch("/api/editorai", {
- method: "POST",
- body: JSON.stringify({
- context: text,
- request: request[idx],
- }),
- });
-
- const reader = resp.body?.getReader();
- let done = false;
- let position = to;
- while (!done && reader) {
- const { value, done: d } = await reader.read();
- done = d;
-
- const decoded = new TextDecoder().decode(value);
- console.log(decoded);
- editor
- .chain()
- .focus()
- .insertContentAt(position + 1, decoded)
- .run();
- position += decoded.length;
- }
-
- setGeneratingfn(false);
-}
diff --git a/apps/web/app/(editor)/components/editorcommands.tsx b/apps/web/app/(editor)/components/editorcommands.tsx
deleted file mode 100644
index 8e80df46..00000000
--- a/apps/web/app/(editor)/components/editorcommands.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import React, { useState } from "react";
-import {
- EditorBubble,
- EditorCommand,
- EditorCommandEmpty,
- EditorCommandItem,
- EditorCommandList,
-} from "novel";
-import { suggestionItems } from "./slash-command";
-import { Separator } from "@repo/ui/shadcn/separator";
-import { NodeSelector } from "./selectors/node-selector";
-import { LinkSelector } from "./selectors/link-selector";
-import { TextButtons } from "./selectors/text-buttons";
-import { ColorSelector } from "./selectors/color-selector";
-import { BgColorSelector } from "./selectors/bgcolor-selector";
-
-function EditorCommands() {
- return (
- <>
- <SlashCommand />
- <PopupMenu />
- </>
- );
-}
-
-function SlashCommand() {
- return (
- <EditorCommand className="z-50 h-auto max-h-[330px] min-w-[20rem] overflow-y-auto rounded-lg bg-[#1F2428] shadow-md transition-all">
- <EditorCommandEmpty className="px-4 text-lg text-muted-foreground">
- No results
- </EditorCommandEmpty>
- <EditorCommandList>
- {suggestionItems.map((item) => (
- <EditorCommandItem
- value={item.title}
- onCommand={(val) => item.command(val)}
- className="flex w-full items-center space-x-4 rounded-md px-4 py-3 text-left text-sm hover:bg-accent aria-selected:bg-[#21303D] group/command"
- key={item.title}
- >
- <div className="flex h-11 w-11 items-center justify-center rounded-md bg-[#2D343A] group-aria-selected/command:bg-[#369DFD33] stroke-[#989EA4] group-aria-selected/command:stroke-[#369DFD]">
- {item.icon}
- </div>
- <div>
- <p className="font-medium text-[#FFFFFF] group-aria-selected/command:text-[#369DFD]">
- {item.title}
- </p>
- <p className="text-xs text-muted-foreground group-aria-selected/command:text-[#369DFDB2]">
- {item.description}
- </p>
- </div>
- </EditorCommandItem>
- ))}
- </EditorCommandList>
- </EditorCommand>
- );
-}
-
-function PopupMenu() {
- const [openNode, setOpenNode] = useState(false);
- const [openColor, setOpenColor] = useState(false);
- const [openBgColor, setOpenBgColor] = useState(false);
- const [openLink, setOpenLink] = useState(false);
- const [openMenu, setOpenMenu] = useState(false);
- return (
- <EditorBubble
- tippyOptions={{
- placement: openMenu ? "bottom-start" : "top",
- }}
- className="flex w-fit max-w-[90vw] overflow-hidden bg-[#1F2428] text-white rounded "
- >
- <Separator orientation="vertical" />
- <NodeSelector open={openNode} onOpenChange={setOpenNode} />
- <Separator orientation="vertical" />
- <LinkSelector open={openLink} onOpenChange={setOpenLink} />
- <Separator orientation="vertical" />
- <TextButtons />
- <Separator orientation="vertical" />
- <ColorSelector open={openColor} onOpenChange={setOpenColor} />
- <Separator orientation="vertical" />
- <BgColorSelector open={openBgColor} onOpenChange={setOpenBgColor} />
- </EditorBubble>
- );
-}
-
-export default EditorCommands;
diff --git a/apps/web/app/(editor)/components/extensions.ts b/apps/web/app/(editor)/components/extensions.ts
deleted file mode 100644
index 0c581154..00000000
--- a/apps/web/app/(editor)/components/extensions.ts
+++ /dev/null
@@ -1,141 +0,0 @@
-import {
- AIHighlight,
- CharacterCount,
- CodeBlockLowlight,
- GlobalDragHandle,
- HorizontalRule,
- Placeholder,
- StarterKit,
- TaskItem,
- TaskList,
- TiptapImage,
- TiptapLink,
- UpdatedImage,
- Youtube,
-} from "novel/extensions";
-import { UploadImagesPlugin } from "novel/plugins";
-
-import { cx } from "class-variance-authority";
-import { common, createLowlight } from "lowlight";
-
-//TODO I am using cx here to get tailwind autocomplete working, idk if someone else can write a regex to just capture the class key in objects
-const aiHighlight = AIHighlight;
-//You can overwrite the placeholder with your own configuration
-const placeholder = Placeholder;
-const tiptapLink = TiptapLink.configure({
- HTMLAttributes: {
- class: cx(
- "text-muted-foreground underline underline-offset-[3px] hover:text-primary transition-colors cursor-pointer",
- ),
- },
-});
-
-const tiptapImage = TiptapImage.extend({
- addProseMirrorPlugins() {
- return [
- UploadImagesPlugin({
- imageClass: cx("opacity-40 rounded-lg border border-stone-200"),
- }),
- ];
- },
-}).configure({
- allowBase64: true,
- HTMLAttributes: {
- class: cx("rounded-lg border border-muted"),
- },
-});
-
-const updatedImage = UpdatedImage.configure({
- HTMLAttributes: {
- class: cx("rounded-lg border border-muted"),
- },
-});
-
-const taskList = TaskList.configure({
- HTMLAttributes: {
- class: cx("not-prose pl-2 "),
- },
-});
-const taskItem = TaskItem.configure({
- HTMLAttributes: {
- class: cx("flex gap-2 items-start my-4"),
- },
- nested: true,
-});
-
-const horizontalRule = HorizontalRule.configure({
- HTMLAttributes: {
- class: cx("mt-4 mb-6 border-t border-muted-foreground"),
- },
-});
-
-const starterKit = StarterKit.configure({
- bulletList: {
- HTMLAttributes: {
- class: cx("list-disc list-outside leading-3 -mt-2"),
- },
- },
- orderedList: {
- HTMLAttributes: {
- class: cx("list-decimal list-outside leading-3 -mt-2"),
- },
- },
- listItem: {
- HTMLAttributes: {
- class: cx("leading-normal -mb-2"),
- },
- },
- blockquote: {
- HTMLAttributes: {
- class: cx("border-l-4 border-primary"),
- },
- },
- codeBlock: {
- HTMLAttributes: {
- class: cx("rounded-md bg-muted text-muted-foreground border p-5 font-mono font-medium"),
- },
- },
- code: {
- HTMLAttributes: {
- class: cx("rounded-md bg-muted px-1.5 py-1 font-mono font-medium"),
- spellcheck: "false",
- },
- },
- horizontalRule: false,
- dropcursor: {
- color: "#DBEAFE",
- width: 4,
- },
- gapcursor: false,
-});
-
-const codeBlockLowlight = CodeBlockLowlight.configure({
- // configure lowlight: common / all / use highlightJS in case there is a need to specify certain language grammars only
- // common: covers 37 language grammars which should be good enough in most cases
- lowlight: createLowlight(common),
-});
-
-const youtube = Youtube.configure({
- HTMLAttributes: {
- class: cx("rounded-lg border border-muted"),
- },
- inline: false,
-});
-
-const characterCount = CharacterCount.configure();
-
-export const defaultExtensions = [
- starterKit,
- placeholder,
- tiptapLink,
- tiptapImage,
- updatedImage,
- taskList,
- taskItem,
- horizontalRule,
- aiHighlight,
- codeBlockLowlight,
- youtube,
- characterCount,
- GlobalDragHandle,
-];
diff --git a/apps/web/app/(editor)/components/image-upload.ts b/apps/web/app/(editor)/components/image-upload.ts
deleted file mode 100644
index d10be168..00000000
--- a/apps/web/app/(editor)/components/image-upload.ts
+++ /dev/null
@@ -1,50 +0,0 @@
-import { createImageUpload } from "novel/plugins";
-import { toast } from "sonner";
-
-const onUpload = (file: File) => {
- //Endpoint: to upload the image
- const promise = fetch("", {
- method: "POST",
- body: file,
- });
-
- return new Promise((resolve, reject) => {
- toast.promise(
- promise.then(async (res) => {
- if (res.status === 200) {
- const { url } = (await res.json()) as { url: string };
- const image = new Image();
- image.src = url;
- image.onload = () => {
- resolve(url);
- };
- } else {
- throw new Error("Error uploading image. Please try again.");
- }
- }),
- {
- loading: "Uploading image...",
- success: "Image uploaded successfully.",
- error: (e) => {
- reject(e);
- return e.message;
- },
- },
- );
- });
-};
-
-export const uploadFn = createImageUpload({
- onUpload,
- validateFn: (file) => {
- if (!file.type.includes("image/")) {
- toast.error("File type not supported.");
- return false;
- }
- if (file.size / 1024 / 1024 > 20) {
- toast.error("File size too big (max 20MB).");
- return false;
- }
- return true;
- },
-});
diff --git a/apps/web/app/(editor)/components/selectors/bgcolor-selector.tsx b/apps/web/app/(editor)/components/selectors/bgcolor-selector.tsx
deleted file mode 100644
index 77da0f03..00000000
--- a/apps/web/app/(editor)/components/selectors/bgcolor-selector.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import { Check, ChevronDown } from "lucide-react";
-import { EditorBubbleItem, useEditor } from "novel";
-
-import { Button } from "@repo/ui/shadcn/button";
-import { Popover, PopoverContent, PopoverTrigger } from "@repo/ui/shadcn/popover";
-export interface BubbleColorMenuItem {
- name: string;
- color: string;
-}
-
-const HIGHLIGHT_COLORS: BubbleColorMenuItem[] = [
- {
- name: "Default",
- color: "var(--novel-highlight-default)",
- },
- {
- name: "Purple",
- color: "var(--novel-highlight-purple)",
- },
- {
- name: "Red",
- color: "var(--novel-highlight-red)",
- },
- {
- name: "Yellow",
- color: "var(--novel-highlight-yellow)",
- },
- {
- name: "Blue",
- color: "var(--novel-highlight-blue)",
- },
- {
- name: "Green",
- color: "var(--novel-highlight-green)",
- },
- {
- name: "Orange",
- color: "var(--novel-highlight-orange)",
- },
- {
- name: "Pink",
- color: "var(--novel-highlight-pink)",
- },
- {
- name: "Gray",
- color: "var(--novel-highlight-gray)",
- },
-];
-
-interface ColorSelectorProps {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export const BgColorSelector = ({ open, onOpenChange }: ColorSelectorProps) => {
- const { editor } = useEditor();
-
- if (!editor) return null;
-
- const activeHighlightItem = HIGHLIGHT_COLORS.find(({ color }) => editor.isActive("highlight", { color }));
-
- return (
- <Popover modal={true} open={open} onOpenChange={onOpenChange}>
- <PopoverTrigger asChild>
- <Button size="sm" className="gap-2 rounded-none" variant="ghost">
- <span
- className="rounded-sm px-1"
- style={{
- backgroundColor: activeHighlightItem?.color,
- }}
- >
- A
- </span>
- <ChevronDown className="h-4 w-4" />
- </Button>
- </PopoverTrigger>
-
- <PopoverContent
- sideOffset={5}
- className="my-1 border-none bg-[#1F2428] flex max-h-80 w-48 flex-col overflow-hidden overflow-y-auto rounded border p-1 shadow-xl "
- align="start"
- >
- <div>
- <div className="my-1 px-2 text-sm font-semibold text-muted-foreground">Background</div>
- {HIGHLIGHT_COLORS.map(({ name, color }) => (
- <EditorBubbleItem
- key={name}
- onSelect={() => {
- editor.commands.unsetHighlight();
- name !== "Default" && editor.commands.setHighlight({ color });
- }}
- className="flex cursor-pointer items-center justify-between px-2 py-1 text-sm hover:bg-[#21303D]"
- >
- <div className="flex items-center gap-2">
- <div className="rounded-sm px-2 py-px font-medium" style={{ backgroundColor: color }}>
- A
- </div>
- <span>{name}</span>
- </div>
- {editor.isActive("highlight", { color }) && <Check className="h-4 w-4" />}
- </EditorBubbleItem>
- ))}
- </div>
- </PopoverContent>
- </Popover>
- );
-};
diff --git a/apps/web/app/(editor)/components/selectors/color-selector.tsx b/apps/web/app/(editor)/components/selectors/color-selector.tsx
deleted file mode 100644
index 557c4255..00000000
--- a/apps/web/app/(editor)/components/selectors/color-selector.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-import { Check, ChevronDown } from "lucide-react";
-import { EditorBubbleItem, useEditor } from "novel";
-
-import { Button } from "@repo/ui/shadcn/button";
-import { Popover, PopoverContent, PopoverTrigger } from "@repo/ui/shadcn/popover";
-export interface BubbleColorMenuItem {
- name: string;
- color: string;
-}
-
-const TEXT_COLORS: BubbleColorMenuItem[] = [
- {
- name: "Default",
- color: "var(--novel-black)",
- },
- {
- name: "Purple",
- color: "#9333EA",
- },
- {
- name: "Red",
- color: "#E00000",
- },
- {
- name: "Yellow",
- color: "#EAB308",
- },
- {
- name: "Blue",
- color: "#2563EB",
- },
- {
- name: "Green",
- color: "#008A00",
- },
- {
- name: "Orange",
- color: "#FFA500",
- },
- {
- name: "Pink",
- color: "#BA4081",
- },
- {
- name: "Gray",
- color: "#A8A29E",
- },
-];
-
-
-interface ColorSelectorProps {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export const ColorSelector = ({ open, onOpenChange }: ColorSelectorProps) => {
- const { editor } = useEditor();
-
- if (!editor) return null;
- const activeColorItem = TEXT_COLORS.find(({ color }) => editor.isActive("textStyle", { color }));
-
- return (
- <Popover modal={true} open={open} onOpenChange={onOpenChange}>
- <PopoverTrigger asChild>
- <Button size="sm" className="gap-2 rounded-none" variant="ghost">
- <span
- className="rounded-sm px-1"
- style={{
- color: activeColorItem?.color
- }}
- >
- A
- </span>
- <ChevronDown className="h-4 w-4" />
- </Button>
- </PopoverTrigger>
-
- <PopoverContent
- sideOffset={5}
- className="my-1 border-none bg-[#1F2428] flex max-h-80 w-48 flex-col overflow-hidden overflow-y-auto rounded border p-1 shadow-xl "
- align="start"
- >
- <div className="flex flex-col">
- <div className="my-1 px-2 text-sm font-semibold text-muted-foreground">Color</div>
- {TEXT_COLORS.map(({ name, color }) => (
- <EditorBubbleItem
- key={name}
- onSelect={() => {
- editor.commands.unsetColor();
- name !== "Default" &&
- editor
- .chain()
- .focus()
- .setColor(color || "")
- .run();
- }}
- className="flex cursor-pointer items-center justify-between px-2 py-1 text-sm hover:bg-accent"
- >
- <div className="flex items-center gap-2">
- <div className="rounded-sm px-2 py-px font-medium" style={{ color }}>
- A
- </div>
- <span>{name}</span>
- </div>
- </EditorBubbleItem>
- ))}
- </div>
- </PopoverContent>
- </Popover>
- );
-};
diff --git a/apps/web/app/(editor)/components/selectors/link-selector.tsx b/apps/web/app/(editor)/components/selectors/link-selector.tsx
deleted file mode 100644
index 3dc28266..00000000
--- a/apps/web/app/(editor)/components/selectors/link-selector.tsx
+++ /dev/null
@@ -1,95 +0,0 @@
-import { Button } from "@repo/ui/shadcn/button";
-import { Popover, PopoverContent, PopoverTrigger } from "@repo/ui/shadcn/popover";
-import { cn } from "@repo/ui/lib/utils";
-import { Check, Trash } from "lucide-react";
-import { useEditor } from "novel";
-import { useEffect, useRef } from "react";
-
-export function isValidUrl(url: string) {
- try {
- new URL(url);
- return true;
- } catch (_e) {
- return false;
- }
-}
-export function getUrlFromString(str: string) {
- if (isValidUrl(str)) return str;
- try {
- if (str.includes(".") && !str.includes(" ")) {
- return new URL(`https://${str}`).toString();
- }
- } catch (_e) {
- return null;
- }
-}
-interface LinkSelectorProps {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export const LinkSelector = ({ open, onOpenChange }: LinkSelectorProps) => {
- const inputRef = useRef<HTMLInputElement>(null);
- const { editor } = useEditor();
-
- // Autofocus on input by default
- useEffect(() => {
- inputRef.current?.focus();
- });
- if (!editor) return null;
-
- return (
- <Popover modal={true} open={open} onOpenChange={onOpenChange}>
- <PopoverTrigger asChild>
- <Button size="sm" variant="ghost" className="gap-2 rounded-none border-none">
- <p className="text-base">↗</p>
- <p
- className={cn("underline decoration-stone-400 underline-offset-4", {
- "text-blue-500": editor.isActive("link"),
- })}
- >
- Link
- </p>
- </Button>
- </PopoverTrigger>
- <PopoverContent align="start" className="w-60 p-0" sideOffset={10}>
- <form
- onSubmit={(e) => {
- const target = e.currentTarget as HTMLFormElement;
- e.preventDefault();
- const input = target[0] as HTMLInputElement;
- const url = getUrlFromString(input.value);
- url && editor.chain().focus().setLink({ href: url }).run();
- }}
- className="flex p-1 "
- >
- <input
- ref={inputRef}
- type="text"
- placeholder="Paste a link"
- className="flex-1 bg-background p-1 text-sm outline-none"
- defaultValue={editor.getAttributes("link").href || ""}
- />
- {editor.getAttributes("link").href ? (
- <Button
- size="icon"
- variant="outline"
- type="button"
- className="flex h-8 items-center rounded-sm p-1 text-red-600 transition-all hover:bg-red-100 dark:hover:bg-red-800"
- onClick={() => {
- editor.chain().focus().unsetLink().run();
- inputRef.current.value = "";
- }}
- >
- <Trash className="h-4 w-4" />
- </Button>
- ) : (
- <Button size="icon" className="h-8">
- <Check className="h-4 w-4" />
- </Button>
- )}
- </form>
- </PopoverContent>
- </Popover>
- );
-};
diff --git a/apps/web/app/(editor)/components/selectors/node-selector.tsx b/apps/web/app/(editor)/components/selectors/node-selector.tsx
deleted file mode 100644
index c6092b68..00000000
--- a/apps/web/app/(editor)/components/selectors/node-selector.tsx
+++ /dev/null
@@ -1,126 +0,0 @@
-import {
- Check,
- CheckSquare,
- ChevronDown,
- Code,
- Heading1,
- Heading2,
- Heading3,
- ListOrdered,
- type LucideIcon,
- TextIcon,
- TextQuote,
-} from "lucide-react";
-import { EditorBubbleItem, useEditor } from "novel";
-
-import { Button } from "@repo/ui/shadcn/button";
-import { Popover, PopoverContent, PopoverTrigger } from "@repo/ui/shadcn/popover";
-
-export type SelectorItem = {
- name: string;
- icon: LucideIcon;
- command: (editor: ReturnType<typeof useEditor>["editor"]) => void;
- isActive: (editor: ReturnType<typeof useEditor>["editor"]) => boolean;
-};
-
-const items: SelectorItem[] = [
- {
- name: "Text",
- icon: TextIcon,
- command: (editor) => editor.chain().focus().clearNodes().run(),
- // I feel like there has to be a more efficient way to do this – feel free to PR if you know how!
- isActive: (editor) =>
- editor.isActive("paragraph") && !editor.isActive("bulletList") && !editor.isActive("orderedList"),
- },
- {
- name: "Heading 1",
- icon: Heading1,
- command: (editor) => editor.chain().focus().clearNodes().toggleHeading({ level: 1 }).run(),
- isActive: (editor) => editor.isActive("heading", { level: 1 }),
- },
- {
- name: "Heading 2",
- icon: Heading2,
- command: (editor) => editor.chain().focus().clearNodes().toggleHeading({ level: 2 }).run(),
- isActive: (editor) => editor.isActive("heading", { level: 2 }),
- },
- {
- name: "Heading 3",
- icon: Heading3,
- command: (editor) => editor.chain().focus().clearNodes().toggleHeading({ level: 3 }).run(),
- isActive: (editor) => editor.isActive("heading", { level: 3 }),
- },
- {
- name: "To-do List",
- icon: CheckSquare,
- command: (editor) => editor.chain().focus().clearNodes().toggleTaskList().run(),
- isActive: (editor) => editor.isActive("taskItem"),
- },
- {
- name: "Bullet List",
- icon: ListOrdered,
- command: (editor) => editor.chain().focus().clearNodes().toggleBulletList().run(),
- isActive: (editor) => editor.isActive("bulletList"),
- },
- {
- name: "Numbered List",
- icon: ListOrdered,
- command: (editor) => editor.chain().focus().clearNodes().toggleOrderedList().run(),
- isActive: (editor) => editor.isActive("orderedList"),
- },
- {
- name: "Quote",
- icon: TextQuote,
- command: (editor) => editor.chain().focus().clearNodes().toggleBlockquote().run(),
- isActive: (editor) => editor.isActive("blockquote"),
- },
- {
- name: "Code",
- icon: Code,
- command: (editor) => editor.chain().focus().clearNodes().toggleCodeBlock().run(),
- isActive: (editor) => editor.isActive("codeBlock"),
- },
-];
-interface NodeSelectorProps {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export const NodeSelector = ({ open, onOpenChange }: NodeSelectorProps) => {
- const { editor } = useEditor();
- if (!editor) return null;
- const activeItem = items.filter((item) => item.isActive(editor)).pop() ?? {
- name: "Multiple",
- };
-
- return (
- <Popover modal={true} open={open} onOpenChange={onOpenChange}>
- <PopoverTrigger asChild className="gap-2 rounded-none border-none hover:bg-accent focus:ring-0">
- <Button size="sm" variant="ghost" className="gap-2">
- <span className="whitespace-nowrap text-sm">{activeItem.name}</span>
- <ChevronDown className="h-4 w-4" />
- </Button>
- </PopoverTrigger>
- <PopoverContent sideOffset={5} align="start" className="w-48 p-1 border-none bg-[#1F2428]">
- {items.map((item) => (
- <EditorBubbleItem
- key={item.name}
- onSelect={(editor) => {
- item.command(editor);
- onOpenChange(false);
- }}
- className="flex cursor-pointer items-center justify-between rounded-sm px-2 py-1 text-sm hover:bg-accent"
- >
- <div className="flex items-center space-x-2">
- <div className="rounded-sm p-1">
- <item.icon className="h-3 w-3" />
- </div>
- <span>{item.name}</span>
- </div>
- {activeItem.name === item.name && <Check className="h-4 w-4" />}
- </EditorBubbleItem>
- ))}
- </PopoverContent>
- </Popover>
- );
-};
diff --git a/apps/web/app/(editor)/components/selectors/text-buttons.tsx b/apps/web/app/(editor)/components/selectors/text-buttons.tsx
deleted file mode 100644
index f75d7b3c..00000000
--- a/apps/web/app/(editor)/components/selectors/text-buttons.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { Button } from "@repo/ui/shadcn/button";
-import { Popover, PopoverContent, PopoverTrigger } from "@repo/ui/shadcn/popover";
-import { BoldIcon, CodeIcon, ItalicIcon, StrikethroughIcon, UnderlineIcon } from "lucide-react";
-import { EditorBubbleItem, useEditor } from "novel";
-import type { SelectorItem } from "./node-selector";
-import { cn } from "@repo/ui/lib/utils";
-
-export const TextButtons = () => {
- const { editor } = useEditor();
- if (!editor) return null;
- const items: SelectorItem[] = [
- {
- name: "bold",
- isActive: (editor) => editor.isActive("bold"),
- command: (editor) => editor.chain().focus().toggleBold().run(),
- icon: BoldIcon,
- },
- {
- name: "italic",
- isActive: (editor) => editor.isActive("italic"),
- command: (editor) => editor.chain().focus().toggleItalic().run(),
- icon: ItalicIcon,
- },
- {
- name: "underline",
- isActive: (editor) => editor.isActive("underline"),
- command: (editor) => editor.chain().focus().toggleUnderline().run(),
- icon: UnderlineIcon,
- },
- {
- name: "strike",
- isActive: (editor) => editor.isActive("strike"),
- command: (editor) => editor.chain().focus().toggleStrike().run(),
- icon: StrikethroughIcon,
- },
- {
- name: "code",
- isActive: (editor) => editor.isActive("code"),
- command: (editor) => editor.chain().focus().toggleCode().run(),
- icon: CodeIcon,
- },
- ];
- return (
- <div className="flex">
- {items.map((item) => (
- <EditorBubbleItem
- key={item.name}
- onSelect={(editor) => {
- item.command(editor);
- }}
- >
- <Button size="sm" className="rounded-none" variant="ghost">
- <item.icon
- className={cn("h-4 w-4", {
- "text-blue-500": item.isActive(editor),
- })}
- />
- </Button>
- </EditorBubbleItem>
- ))}
- </div>
- );
-};
diff --git a/apps/web/app/(editor)/components/slash-command.tsx b/apps/web/app/(editor)/components/slash-command.tsx
deleted file mode 100644
index 1bfb1690..00000000
--- a/apps/web/app/(editor)/components/slash-command.tsx
+++ /dev/null
@@ -1,163 +0,0 @@
-import {
- CheckSquare,
- Code,
- Heading1,
- Heading2,
- Heading3,
- ImageIcon,
- List,
- ListOrdered,
- MessageSquarePlus,
- Text,
- TextQuote,
- Youtube
-} from "lucide-react";
-import { createSuggestionItems } from "novel/extensions";
-import { Command, renderItems } from "novel/extensions";
-import { uploadFn } from "./image-upload";
-
-export const suggestionItems = createSuggestionItems([
- {
- title: "Send Feedback",
- description: "Let us know how we can improve.",
- icon: <MessageSquarePlus stroke="inherit" size={18} />,
- command: ({ editor, range }) => {
- editor.chain().focus().deleteRange(range).run();
- window.open("/feedback", "_blank");
- },
- },
- {
- title: "Text",
- description: "Just start typing with plain text.",
- searchTerms: ["p", "paragraph"],
- icon: <Text stroke="inherit" size={18} />,
- command: ({ editor, range }) => {
- editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").run();
- },
- },
- {
- title: "To-do List",
- description: "Track tasks with a to-do list.",
- searchTerms: ["todo", "task", "list", "check", "checkbox"],
- icon: <CheckSquare stroke="inherit" size={18} />,
- command: ({ editor, range }) => {
- editor.chain().focus().deleteRange(range).toggleTaskList().run();
- },
- },
- {
- title: "Heading 1",
- description: "Big section heading.",
- searchTerms: ["title", "big", "large"],
- icon: <Heading1 stroke="inherit" size={18} />,
- command: ({ editor, range }) => {
- editor.chain().focus().deleteRange(range).setNode("heading", { level: 1 }).run();
- },
- },
- {
- title: "Heading 2",
- description: "Medium section heading.",
- searchTerms: ["subtitle", "medium"],
- icon: <Heading2 stroke="inherit" size={18} />,
- command: ({ editor, range }) => {
- editor.chain().focus().deleteRange(range).setNode("heading", { level: 2 }).run();
- },
- },
- {
- title: "Heading 3",
- description: "Small section heading.",
- searchTerms: ["subtitle", "small"],
- icon: <Heading3 stroke="inherit" size={18} />,
- command: ({ editor, range }) => {
- editor.chain().focus().deleteRange(range).setNode("heading", { level: 3 }).run();
- },
- },
- {
- title: "Bullet List",
- description: "Create a simple bullet list.",
- searchTerms: ["unordered", "point"],
- icon: <List stroke="inherit" size={18} />,
- command: ({ editor, range }) => {
- editor.chain().focus().deleteRange(range).toggleBulletList().run();
- },
- },
- {
- title: "Numbered List",
- description: "Create a list with numbering.",
- searchTerms: ["ordered"],
- icon: <ListOrdered stroke="inherit" size={18} />,
- command: ({ editor, range }) => {
- editor.chain().focus().deleteRange(range).toggleOrderedList().run();
- },
- },
- {
- title: "Quote",
- description: "Capture a quote.",
- searchTerms: ["blockquote"],
- icon: <TextQuote stroke="inherit" size={18} />,
- command: ({ editor, range }) =>
- editor.chain().focus().deleteRange(range).toggleNode("paragraph", "paragraph").toggleBlockquote().run(),
- },
- {
- title: "Code",
- description: "Capture a code snippet.",
- searchTerms: ["codeblock"],
- icon: <Code stroke="inherit" size={18} />,
- command: ({ editor, range }) => editor.chain().focus().deleteRange(range).toggleCodeBlock().run(),
- },
- // {
- // title: "Image",
- // description: "Upload an image from your computer.",
- // searchTerms: ["photo", "picture", "media"],
- // icon: <ImageIcon stroke="inherit" size={18} />,
- // command: ({ editor, range }) => {
- // editor.chain().focus().deleteRange(range).run();
- // // upload image
- // const input = document.createElement("input");
- // input.type = "file";
- // input.accept = "image/*";
- // input.onchange = async () => {
- // if (input.files?.length) {
- // const file = input.files[0];
- // const pos = editor.view.state.selection.from;
- // uploadFn(file, editor.view, pos);
- // }
- // };
- // input.click();
- // },
- // },
- // {
- // title: "Youtube",
- // description: "Embed a Youtube video.",
- // searchTerms: ["video", "youtube", "embed"],
- // icon: <Youtube stroke="inherit" size={18} />,
- // command: ({ editor, range }) => {
- // const videoLink = prompt("Please enter Youtube Video Link");
- // //From https://regexr.com/3dj5t
- // const ytregex = new RegExp(
- // /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/,
- // );
-
- // if (ytregex.test(videoLink)) {
- // editor
- // .chain()
- // .focus()
- // .deleteRange(range)
- // .setYoutubeVideo({
- // src: videoLink,
- // })
- // .run();
- // } else {
- // if (videoLink !== null) {
- // alert("Please enter a correct Youtube Video Link");
- // }
- // }
- // },
- // },
-]);
-
-export const slashCommand = Command.configure({
- suggestion: {
- items: () => suggestionItems,
- render: renderItems,
- },
-});
diff --git a/apps/web/app/(editor)/components/topbar.tsx b/apps/web/app/(editor)/components/topbar.tsx
deleted file mode 100644
index 49b5179c..00000000
--- a/apps/web/app/(editor)/components/topbar.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-"use client";
-
-import {
- AnimatePresence,
- useMotionValueEvent,
- useScroll,
- motion,
-} from "framer-motion";
-import React, { useState } from "react";
-
-function Topbar({
- charsCount,
- saveStatus,
-}: {
- charsCount: number | undefined;
- saveStatus: string;
-}) {
- const [visible, setVisible] = useState(true);
-
- const { scrollYProgress } = useScroll();
- useMotionValueEvent(scrollYProgress, "change", (current) => {
- if (typeof current === "number") {
- let direction = current! - scrollYProgress.getPrevious()!;
-
- if (direction < 0 || direction === 1) {
- setVisible(true);
- } else {
- setVisible(false);
- }
- }
- });
- return (
- <div className="fixed left-0 top-0 z-10">
- <AnimatePresence mode="wait">
- <motion.div
- initial={{
- opacity: 1,
- y: -150,
- }}
- animate={{
- y: visible ? 0 : -150,
- opacity: visible ? 1 : 0,
- }}
- transition={{
- duration: 0.2,
- }}
- className="flex flex-col items-center"
- >
- <div className="gap-2 w-screen flex bg-[#171B1F] justify-center items-center pt-6 pb-4">
- <div className="rounded-lg bg-[#21303D] px-2 py-1 text-sm text-muted-foreground">
- Untitled
- </div>
- <div className="rounded-lg bg-[#21303D] px-2 py-1 text-sm text-muted-foreground">
- {saveStatus}
- </div>
- {charsCount && (
- <div className="rounded-lg bg-[#21303D] px-2 py-1 text-sm text-muted-foreground">
- {`${charsCount} words`}
- </div>
- )}
- </div>
- </motion.div>
- </AnimatePresence>
- </div>
- );
-}
-
-export default Topbar;
diff --git a/apps/web/app/(editor)/components/ui/asksvg.tsx b/apps/web/app/(editor)/components/ui/asksvg.tsx
deleted file mode 100644
index aa38fe08..00000000
--- a/apps/web/app/(editor)/components/ui/asksvg.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react'
-
-function Asksvg() {
- return (
- <svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5.90925 4.63925C6.7875 3.8705 8.2125 3.8705 9.09075 4.63925C9.96975 5.408 9.96975 6.6545 9.09075 7.42325C8.9385 7.5575 8.76825 7.66775 8.58825 7.75475C8.0295 8.0255 7.50075 8.504 7.50075 9.125V9.6875M14.25 8C14.25 8.88642 14.0754 9.76417 13.7362 10.5831C13.397 11.4021 12.8998 12.1462 12.273 12.773C11.6462 13.3998 10.9021 13.897 10.0831 14.2362C9.26417 14.5754 8.38642 14.75 7.5 14.75C6.61358 14.75 5.73583 14.5754 4.91689 14.2362C4.09794 13.897 3.35382 13.3998 2.72703 12.773C2.10023 12.1462 1.60303 11.4021 1.26381 10.5831C0.924594 9.76417 0.75 8.88642 0.75 8C0.75 6.20979 1.46116 4.4929 2.72703 3.22703C3.9929 1.96116 5.70979 1.25 7.5 1.25C9.29021 1.25 11.0071 1.96116 12.273 3.22703C13.5388 4.4929 14.25 6.20979 14.25 8ZM7.5 11.9375H7.506V11.9435H7.5V11.9375Z" stroke="#989EA4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-</svg>
-
- )
-}
-
-export default Asksvg \ No newline at end of file
diff --git a/apps/web/app/(editor)/components/ui/autocompletesvg.tsx b/apps/web/app/(editor)/components/ui/autocompletesvg.tsx
deleted file mode 100644
index c433fcad..00000000
--- a/apps/web/app/(editor)/components/ui/autocompletesvg.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react'
-
-function Autocompletesvg() {
- return (
- <svg width="15" height="10" viewBox="0 0 15 10" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M1.3125 1.0625H13.6875M1.3125 5H13.6875M1.3125 8.9375H7.5" stroke="#989EA4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-</svg>
-
- )
-}
-
-export default Autocompletesvg \ No newline at end of file
diff --git a/apps/web/app/(editor)/components/ui/crazy-spinner.tsx b/apps/web/app/(editor)/components/ui/crazy-spinner.tsx
deleted file mode 100644
index 2e95deee..00000000
--- a/apps/web/app/(editor)/components/ui/crazy-spinner.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-const CrazySpinner = () => {
- return (
- <div className="flex justify-center items-center gap-1.5">
- <div className="h-1.5 w-1.5 animate-ping rounded-full bg-[#369DFD] [animation-delay:-0.4s]" />
- <div className="h-1.5 w-1.5 animate-ping rounded-full bg-[#369DFD] [animation-delay:-0.2s]" />
- <div className="h-1.5 w-1.5 animate-ping rounded-full bg-[#369DFD]" />
- </div>
- );
-};
-
-export default CrazySpinner;
diff --git a/apps/web/app/(editor)/components/ui/magic.tsx b/apps/web/app/(editor)/components/ui/magic.tsx
deleted file mode 100644
index 04dce39e..00000000
--- a/apps/web/app/(editor)/components/ui/magic.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-export default function Magic({ className }: { className: string }) {
- return (
-<svg width="18" height="19" className={className} viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path fill-rule="evenodd" clip-rule="evenodd" d="M6.49998 3.25C6.63578 3.25003 6.76788 3.29429 6.87629 3.37608C6.98469 3.45788 7.06351 3.57275 7.10081 3.70333L7.77831 6.075C7.92418 6.58576 8.19785 7.05092 8.57345 7.42652C8.94906 7.80213 9.41421 8.07579 9.92498 8.22167L12.2966 8.89917C12.4271 8.93655 12.5419 9.0154 12.6236 9.1238C12.7053 9.2322 12.7495 9.36425 12.7495 9.5C12.7495 9.63574 12.7053 9.7678 12.6236 9.8762C12.5419 9.9846 12.4271 10.0634 12.2966 10.1008L9.92498 10.7783C9.41421 10.9242 8.94906 11.1979 8.57345 11.5735C8.19785 11.9491 7.92418 12.4142 7.77831 12.925L7.10081 15.2967C7.06343 15.4272 6.98458 15.5419 6.87618 15.6236C6.76778 15.7054 6.63572 15.7495 6.49998 15.7495C6.36423 15.7495 6.23218 15.7054 6.12378 15.6236C6.01538 15.5419 5.93653 15.4272 5.89914 15.2967L5.22164 12.925C5.07577 12.4142 4.80211 11.9491 4.4265 11.5735C4.05089 11.1979 3.58574 10.9242 3.07498 10.7783L0.70331 10.1008C0.572814 10.0634 0.458036 9.9846 0.376329 9.8762C0.294622 9.7678 0.250427 9.63574 0.250427 9.5C0.250427 9.36425 0.294622 9.2322 0.376329 9.1238C0.458036 9.0154 0.572814 8.93655 0.70331 8.89917L3.07498 8.22167C3.58574 8.07579 4.05089 7.80213 4.4265 7.42652C4.80211 7.05092 5.07577 6.58576 5.22164 6.075L5.89914 3.70333C5.93644 3.57275 6.01526 3.45788 6.12367 3.37608C6.23208 3.29429 6.36417 3.25003 6.49998 3.25ZM14 0.75C14.1394 0.749922 14.2749 0.79647 14.3848 0.882239C14.4947 0.968007 14.5728 1.08807 14.6066 1.22333L14.8216 2.08667C15.0183 2.87 15.63 3.48167 16.4133 3.67833L17.2766 3.89333C17.4122 3.9269 17.5325 4.00488 17.6186 4.11483C17.7046 4.22479 17.7514 4.36038 17.7514 4.5C17.7514 4.63962 17.7046 4.77521 17.6186 4.88517C17.5325 4.99512 17.4122 5.0731 17.2766 5.10667L16.4133 5.32167C15.63 5.51833 15.0183 6.13 14.8216 6.91333L14.6066 7.77667C14.5731 7.91219 14.4951 8.03257 14.3851 8.11861C14.2752 8.20465 14.1396 8.2514 14 8.2514C13.8604 8.2514 13.7248 8.20465 13.6148 8.11861C13.5049 8.03257 13.4269 7.91219 13.3933 7.77667L13.1783 6.91333C13.0822 6.52869 12.8833 6.17741 12.6029 5.89706C12.3226 5.61671 11.9713 5.41782 11.5866 5.32167L10.7233 5.10667C10.5878 5.0731 10.4674 4.99512 10.3814 4.88517C10.2953 4.77521 10.2486 4.63962 10.2486 4.5C10.2486 4.36038 10.2953 4.22479 10.3814 4.11483C10.4674 4.00488 10.5878 3.9269 10.7233 3.89333L11.5866 3.67833C11.9713 3.58218 12.3226 3.38329 12.6029 3.10294C12.8833 2.82258 13.0822 2.47131 13.1783 2.08667L13.3933 1.22333C13.4271 1.08807 13.5052 0.968007 13.6152 0.882239C13.7251 0.79647 13.8605 0.749922 14 0.75ZM12.75 12C12.8812 11.9999 13.0092 12.0412 13.1157 12.1179C13.2222 12.1946 13.3018 12.303 13.3433 12.4275L13.6716 13.4133C13.7966 13.7858 14.0883 14.0792 14.4616 14.2033L15.4475 14.5325C15.5716 14.5742 15.6795 14.6538 15.756 14.7601C15.8324 14.8664 15.8736 14.9941 15.8736 15.125C15.8736 15.2559 15.8324 15.3836 15.756 15.4899C15.6795 15.5962 15.5716 15.6758 15.4475 15.7175L14.4616 16.0467C14.0891 16.1717 13.7958 16.4633 13.6716 16.8367L13.3425 17.8225C13.3008 17.9466 13.2212 18.0545 13.1149 18.131C13.0086 18.2075 12.8809 18.2486 12.75 18.2486C12.619 18.2486 12.4914 18.2075 12.3851 18.131C12.2788 18.0545 12.1992 17.9466 12.1575 17.8225L11.8283 16.8367C11.7669 16.6527 11.6636 16.4856 11.5265 16.3485C11.3894 16.2114 11.2222 16.1081 11.0383 16.0467L10.0525 15.7175C9.92834 15.6758 9.82043 15.5962 9.74398 15.4899C9.66752 15.3836 9.6264 15.2559 9.6264 15.125C9.6264 14.9941 9.66752 14.8664 9.74398 14.7601C9.82043 14.6538 9.92834 14.5742 10.0525 14.5325L11.0383 14.2033C11.4108 14.0783 11.7041 13.7867 11.8283 13.4133L12.1575 12.4275C12.1989 12.3031 12.2784 12.1949 12.3848 12.1182C12.4911 12.0414 12.6189 12.0001 12.75 12Z" fill="#369DFD"/>
-</svg>
-
- );
-}
diff --git a/apps/web/app/(editor)/components/ui/rewritesvg.tsx b/apps/web/app/(editor)/components/ui/rewritesvg.tsx
deleted file mode 100644
index fad9eb90..00000000
--- a/apps/web/app/(editor)/components/ui/rewritesvg.tsx
+++ /dev/null
@@ -1,11 +0,0 @@
-import React from 'react'
-
-function Rewritesvg() {
- return (
- <svg width="17" height="14" viewBox="0 0 17 14" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M11.5172 5.01108H15.2612L12.8755 2.62383C12.1074 1.85573 11.1506 1.30337 10.1014 1.02228C9.05214 0.74119 7.94738 0.741272 6.89818 1.02252C5.84897 1.30377 4.8923 1.85627 4.12433 2.62448C3.35635 3.39269 2.80415 4.34954 2.52323 5.39883M1.73873 12.7331V8.98908M1.73873 8.98908H5.48273M1.73873 8.98908L4.12373 11.3763C4.89181 12.1444 5.84857 12.6968 6.89782 12.9779C7.94706 13.259 9.05182 13.2589 10.101 12.9776C11.1502 12.6964 12.1069 12.1439 12.8749 11.3757C13.6428 10.6075 14.1951 9.65062 14.476 8.60133M15.2612 1.26708V5.00958" stroke="#989EA4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-</svg>
- )
-}
-
-export default Rewritesvg \ No newline at end of file
diff --git a/apps/web/app/(editor)/components/ui/translatesvg.tsx b/apps/web/app/(editor)/components/ui/translatesvg.tsx
deleted file mode 100644
index cde82da6..00000000
--- a/apps/web/app/(editor)/components/ui/translatesvg.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import React from 'react'
-
-function Translatesvg() {
- return (
- <svg width="15" height="16" viewBox="0 0 15 16" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M6.375 14.75L10.3125 6.3125L14.25 14.75M7.5 12.5H13.125M0.75 3.21575C2.2428 3.02999 3.74569 2.93706 5.25 2.9375M5.25 2.9375C6.09 2.9375 6.92475 2.966 7.7505 3.023M5.25 2.9375V1.25M7.7505 3.023C6.882 6.9935 4.2675 10.31 0.75 12.1265M7.7505 3.023C8.4225 3.06875 9.08925 3.13325 9.75 3.21575M6.30825 9.587C5.07822 8.33647 4.10335 6.85849 3.438 5.2355" stroke="#989EA4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
-</svg>
-
- )
-}
-
-export default Translatesvg \ No newline at end of file
diff --git a/apps/web/app/(editor)/editor.tsx b/apps/web/app/(editor)/editor.tsx
deleted file mode 100644
index f7f9a098..00000000
--- a/apps/web/app/(editor)/editor.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-"use client";
-import { defaultEditorContent } from "./lib/content";
-import { EditorContent, EditorRoot, type JSONContent } from "novel";
-import { ImageResizer } from "novel/extensions";
-import { useEffect, useState } from "react";
-import { defaultExtensions } from "./components/extensions";
-
-import { slashCommand } from "./components/slash-command";
-import { Updates } from "./lib/debouncedsave";
-import { editorProps } from "./lib/editorprops";
-import EditorCommands from "./components/editorcommands";
-import Aigenerate from "./components/aigenerate";
-import { useMotionValueEvent, useScroll } from "framer-motion";
-import Topbar from "./components/topbar";
-
-const Editor = () => {
- const [initialContent, setInitialContent] = useState<null | JSONContent>(
- null,
- );
- const [saveStatus, setSaveStatus] = useState("Saved");
- const [charsCount, setCharsCount] = useState();
- const [visible, setVisible] = useState(true);
-
- useEffect(() => {
- if (typeof window === "undefined") return;
- const content = window.localStorage.getItem("novel-content");
- if (content) setInitialContent(JSON.parse(content));
- else setInitialContent(defaultEditorContent);
- }, []);
-
- if (!initialContent) return <>Loading...</>;
-
- return (
- <div className="relative w-full max-w-screen-xl">
- <Topbar charsCount={charsCount} saveStatus={saveStatus} />
- <EditorRoot>
- <EditorContent
- initialContent={initialContent}
- extensions={[...defaultExtensions, slashCommand]}
- className="min-h-[55vh] mt-[8vh] w-full max-w-screen-xl bg-[#171B1F] mb-[40vh]"
- editorProps={editorProps}
- onUpdate={({ editor }) => {
- Updates({ editor, setCharsCount, setSaveStatus });
- }}
- slotAfter={<ImageResizer />}
- >
- <EditorCommands />
- <Aigenerate />
- </EditorContent>
- </EditorRoot>
- </div>
- );
-};
-
-export default Editor;
diff --git a/apps/web/app/(editor)/editor/page.tsx b/apps/web/app/(editor)/editor/page.tsx
deleted file mode 100644
index d0298065..00000000
--- a/apps/web/app/(editor)/editor/page.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import TailwindAdvancedEditor from "../editor";
-export default function Page() {
- return (
- <div className="flex min-h-screen flex-col items-center bg-[#171B1F]">
- <TailwindAdvancedEditor />
- </div>
- );
-}
diff --git a/apps/web/app/(editor)/layout.tsx b/apps/web/app/(editor)/layout.tsx
deleted file mode 100644
index 1bf97715..00000000
--- a/apps/web/app/(editor)/layout.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import "./styles/prosemirror.css";
-import "./styles/globals.css"
-import type { ReactNode } from "react";
-
-
-export default function RootLayout({ children }: { children: ReactNode }) {
- return (
- <div className="dark">
- {children}
- </div>
- );
-}
diff --git a/apps/web/app/(editor)/lib/content.ts b/apps/web/app/(editor)/lib/content.ts
deleted file mode 100644
index 6464cfa1..00000000
--- a/apps/web/app/(editor)/lib/content.ts
+++ /dev/null
@@ -1,231 +0,0 @@
-export const defaultEditorContent = {
- type: "doc",
- content: [
- {
- type: "heading",
- attrs: { level: 2 },
- content: [{ type: "text", text: "Introducing Novel" }],
- },
- {
- type: "paragraph",
- content: [
- {
- type: "text",
- marks: [
- {
- type: "link",
- attrs: {
- href: "https://github.com/steven-tey/novel",
- target: "_blank",
- },
- },
- ],
- text: "Novel",
- },
- {
- type: "text",
- text: " is a Notion-style WYSIWYG editor with AI-powered autocompletion. Built with ",
- },
- {
- type: "text",
- marks: [
- {
- type: "link",
- attrs: {
- href: "https://tiptap.dev/",
- target: "_blank",
- },
- },
- ],
- text: "Tiptap",
- },
- { type: "text", text: " + " },
- {
- type: "text",
- marks: [
- {
- type: "link",
- attrs: {
- href: "https://sdk.vercel.ai/docs",
- target: "_blank",
- },
- },
- ],
- text: "Vercel AI SDK",
- },
- { type: "text", text: "." },
- ],
- },
- {
- type: "heading",
- attrs: { level: 3 },
- content: [{ type: "text", text: "Installation" }],
- },
- {
- type: "codeBlock",
- attrs: { language: null },
- content: [{ type: "text", text: "npm i novel" }],
- },
- {
- type: "heading",
- attrs: { level: 3 },
- content: [{ type: "text", text: "Usage" }],
- },
- {
- type: "codeBlock",
- attrs: { language: null },
- content: [
- {
- type: "text",
- text: 'import { Editor } from "novel";\n\nexport default function App() {\n return (\n <Editor />\n )\n}',
- },
- ],
- },
- {
- type: "heading",
- attrs: { level: 3 },
- content: [{ type: "text", text: "Features" }],
- },
- {
- type: "orderedList",
- attrs: { tight: true, start: 1 },
- content: [
- {
- type: "listItem",
- content: [
- {
- type: "paragraph",
- content: [{ type: "text", text: "Slash menu & bubble menu" }],
- },
- ],
- },
- {
- type: "listItem",
- content: [
- {
- type: "paragraph",
- content: [
- { type: "text", text: "AI autocomplete (type " },
- { type: "text", marks: [{ type: "code" }], text: "++" },
- {
- type: "text",
- text: " to activate, or select from slash menu)",
- },
- ],
- },
- ],
- },
- {
- type: "listItem",
- content: [
- {
- type: "paragraph",
- content: [
- {
- type: "text",
- text: "Image uploads (drag & drop / copy & paste, or select from slash menu) ",
- },
- ],
- },
- ],
- },
- ],
- },
- {
- type: "image",
- attrs: {
- src: "https://public.blob.vercel-storage.com/pJrjXbdONOnAeZAZ/banner-2wQk82qTwyVgvlhTW21GIkWgqPGD2C.png",
- alt: "banner.png",
- title: "banner.png",
- width: null,
- height: null,
- },
- },
- { type: "horizontalRule" },
- {
- type: "heading",
- attrs: { level: 3 },
- content: [{ type: "text", text: "Learn more" }],
- },
- {
- type: "taskList",
- content: [
- {
- type: "taskItem",
- attrs: { checked: false },
- content: [
- {
- type: "paragraph",
- content: [
- { type: "text", text: "Star us on " },
- {
- type: "text",
- marks: [
- {
- type: "link",
- attrs: {
- href: "https://github.com/steven-tey/novel",
- target: "_blank",
- },
- },
- ],
- text: "GitHub",
- },
- ],
- },
- ],
- },
- {
- type: "taskItem",
- attrs: { checked: false },
- content: [
- {
- type: "paragraph",
- content: [
- { type: "text", text: "Install the " },
- {
- type: "text",
- marks: [
- {
- type: "link",
- attrs: {
- href: "https://www.npmjs.com/package/novel",
- target: "_blank",
- },
- },
- ],
- text: "NPM package",
- },
- ],
- },
- ],
- },
- {
- type: "taskItem",
- attrs: { checked: false },
- content: [
- {
- type: "paragraph",
- content: [
- {
- type: "text",
- marks: [
- {
- type: "link",
- attrs: {
- href: "https://vercel.com/templates/next.js/novel",
- target: "_blank",
- },
- },
- ],
- text: "Deploy your own",
- },
- { type: "text", text: " to Vercel" },
- ],
- },
- ],
- },
- ],
- },
- ],
-};
diff --git a/apps/web/app/(editor)/lib/debouncedsave.ts b/apps/web/app/(editor)/lib/debouncedsave.ts
deleted file mode 100644
index 6490c6c4..00000000
--- a/apps/web/app/(editor)/lib/debouncedsave.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import hljs from 'highlight.js'
-import { debounce } from 'tldraw';
-import { useDebouncedCallback } from "use-debounce";
-
-export const Updates = debounce(({editor, setCharsCount, setSaveStatus})=> {
- const json = editor.getJSON();
- setCharsCount(editor.storage.characterCount.words());
- window.localStorage.setItem("html-content", highlightCodeblocks(editor.getHTML()));
- window.localStorage.setItem("novel-content", JSON.stringify(json));
- window.localStorage.setItem("markdown", editor.storage.markdown.getMarkdown());
- setSaveStatus("Saved");
-}, 500)
-
-export const highlightCodeblocks = (content: string) => {
- const doc = new DOMParser().parseFromString(content, 'text/html');
- doc.querySelectorAll('pre code').forEach((el) => {
- hljs.highlightElement(el);
- });
- return new XMLSerializer().serializeToString(doc);
-}; \ No newline at end of file
diff --git a/apps/web/app/(editor)/lib/editorprops.ts b/apps/web/app/(editor)/lib/editorprops.ts
deleted file mode 100644
index 00d89264..00000000
--- a/apps/web/app/(editor)/lib/editorprops.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { handleCommandNavigation } from "novel/extensions";
-import { handleImageDrop, handleImagePaste } from "novel/plugins";
-import { uploadFn } from "../components/image-upload";
-import { EditorView } from "prosemirror-view";
-
-export const editorProps = {
- handleDOMEvents: {
- keydown: (_view: EditorView, event: KeyboardEvent) => handleCommandNavigation(event),
- },
- handlePaste: (view: EditorView, event: ClipboardEvent) => handleImagePaste(view, event, uploadFn),
- handleDrop: (view: EditorView, event: DragEvent, slice, moved:boolean) => handleImageDrop(view, event, moved, uploadFn),
- attributes: {
- class:
- "prose prose-lg dark:prose-invert prose-headings:font-title font-default focus:outline-none max-w-full",
- },
-} \ No newline at end of file
diff --git a/apps/web/app/(editor)/lib/use-local-storage.ts b/apps/web/app/(editor)/lib/use-local-storage.ts
deleted file mode 100644
index 5f2ebeb9..00000000
--- a/apps/web/app/(editor)/lib/use-local-storage.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { useEffect, useState } from "react";
-
-const useLocalStorage = <T>(
- key: string,
- initialValue: T,
- // eslint-disable-next-line no-unused-vars
-): [T, (value: T) => void] => {
- const [storedValue, setStoredValue] = useState(initialValue);
-
- useEffect(() => {
- // Retrieve from localStorage
- const item = window.localStorage.getItem(key);
- if (item) {
- setStoredValue(JSON.parse(item));
- }
- }, [key]);
-
- const setValue = (value: T) => {
- // Save state
- setStoredValue(value);
- // Save to localStorage
- window.localStorage.setItem(key, JSON.stringify(value));
- };
- return [storedValue, setValue];
-};
-
-export default useLocalStorage;
diff --git a/apps/web/app/(editor)/styles/globals.css b/apps/web/app/(editor)/styles/globals.css
deleted file mode 100644
index 336e2dae..00000000
--- a/apps/web/app/(editor)/styles/globals.css
+++ /dev/null
@@ -1,168 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
-@layer base {
- :root {
- --background: 0 0% 100%;
- --foreground: 222.2 84% 4.9%;
-
- --card: 0 0% 100%;
- --card-foreground: 222.2 84% 4.9%;
-
- --popover: 0 0% 100%;
- --popover-foreground: 222.2 84% 4.9%;
-
- --primary: 222.2 47.4% 11.2%;
- --primary-foreground: 210 40% 98%;
-
- --secondary: 210 40% 96.1%;
- --secondary-foreground: 222.2 47.4% 11.2%;
-
- --muted: 210 40% 96.1%;
- --muted-foreground: 215.4 16.3% 46.9%;
-
- --accent: 210 40% 96.1%;
- --accent-foreground: 222.2 47.4% 11.2%;
-
- --destructive: 0 84.2% 60.2%;
- --destructive-foreground: 210 40% 98%;
-
- --border: 214.3 31.8% 91.4%;
- --input: 214.3 31.8% 91.4%;
- --ring: 222.2 84% 4.9%;
-
- --radius: 0.5rem;
-
- --novel-highlight-default: #ffffff;
- --novel-highlight-purple: #f6f3f8;
- --novel-highlight-red: #fdebeb;
- --novel-highlight-yellow: #fbf4a2;
- --novel-highlight-blue: #c1ecf9;
- --novel-highlight-green: #acf79f;
- --novel-highlight-orange: #faebdd;
- --novel-highlight-pink: #faf1f5;
- --novel-highlight-gray: #f1f1ef;
- }
-
- .dark {
- --background: #171B1F;
- --foreground: 210 40% 98%;
-
- --card: 222.2 84% 4.9%;
- --card-foreground: 210 40% 98%;
-
- --popover: 222.2 84% 4.9%;
- --popover-foreground: 210 40% 98%;
-
- --primary: 210 40% 98%;
- --primary-foreground: 222.2 47.4% 11.2%;
-
- --secondary: 217.2 32.6% 17.5%;
- --secondary-foreground: 210 40% 98%;
-
- --muted: 217.2 32.6% 17.5%;
- --muted-foreground: 215 20.2% 65.1%;
-
- --accent: 217.2 32.6% 17.5%;
- --accent-foreground: 210 40% 98%;
-
- --destructive: 0 62.8% 30.6%;
- --destructive-foreground: 210 40% 98%;
-
- --border: 217.2 32.6% 17.5%;
- --input: 217.2 32.6% 17.5%;
- --ring: 212.7 26.8% 83.9%;
-
- --novel-highlight-default: #000000;
- --novel-highlight-purple: #3f2c4b;
- --novel-highlight-red: #5c1a1a;
- --novel-highlight-yellow: #5c4b1a;
- --novel-highlight-blue: #1a3d5c;
- --novel-highlight-green: #1a5c20;
- --novel-highlight-orange: #5c3a1a;
- --novel-highlight-pink: #5c1a3a;
- --novel-highlight-gray: #3a3a3a;
- }
-}
-
-@layer base {
- * {
- @apply border-border;
- }
- body {
- @apply bg-background text-foreground;
- }
-}
-
-
-pre {
- background: #0d0d0d;
- border-radius: 0.5rem;
- color: #fff;
- font-family: "JetBrainsMono", monospace;
- padding: 0.75rem 1rem;
-
- code {
- background: none;
- color: inherit;
- font-size: 0.8rem;
- padding: 0;
- }
-
- .hljs-comment,
- .hljs-quote {
- color: #616161;
- }
-
- .hljs-variable,
- .hljs-template-variable,
- .hljs-attribute,
- .hljs-tag,
- .hljs-name,
- .hljs-regexp,
- .hljs-link,
- .hljs-name,
- .hljs-selector-id,
- .hljs-selector-class {
- color: #f98181;
- }
-
- .hljs-number,
- .hljs-meta,
- .hljs-built_in,
- .hljs-builtin-name,
- .hljs-literal,
- .hljs-type,
- .hljs-params {
- color: #fbbc88;
- }
-
- .hljs-string,
- .hljs-symbol,
- .hljs-bullet {
- color: #b9f18d;
- }
-
- .hljs-title,
- .hljs-section {
- color: #faf594;
- }
-
- .hljs-keyword,
- .hljs-selector-tag {
- color: #70cff8;
- }
-
- .hljs-emphasis {
- font-style: italic;
- }
-
- .hljs-strong {
- font-weight: 700;
- }
-}
-::-webkit-scrollbar{
- width: 0;
-}
-
diff --git a/apps/web/app/(editor)/styles/prosemirror.css b/apps/web/app/(editor)/styles/prosemirror.css
deleted file mode 100644
index 7298c98b..00000000
--- a/apps/web/app/(editor)/styles/prosemirror.css
+++ /dev/null
@@ -1,203 +0,0 @@
-.ProseMirror {
- @apply p-12 px-8 sm:px-12;
-}
-
-.ProseMirror .is-editor-empty:first-child::before {
- content: attr(data-placeholder);
- float: left;
- color: hsl(var(--muted-foreground));
- pointer-events: none;
- height: 0;
-}
-.ProseMirror .is-empty::before {
- content: attr(data-placeholder);
- float: left;
- color: hsl(var(--muted-foreground));
- pointer-events: none;
- height: 0;
-}
-
-/* Custom image styles */
-
-.ProseMirror img {
- transition: filter 0.1s ease-in-out;
-
- &:hover {
- cursor: pointer;
- filter: brightness(90%);
- }
-
- &.ProseMirror-selectednode {
- outline: 3px solid #5abbf7;
- filter: brightness(90%);
- }
-}
-
-.img-placeholder {
- position: relative;
-
- &:before {
- content: "";
- box-sizing: border-box;
- position: absolute;
- top: 50%;
- left: 50%;
- width: 36px;
- height: 36px;
- border-radius: 50%;
- border: 3px solid var(--novel-stone-200);
- border-top-color: var(--novel-stone-800);
- animation: spinning 0.6s linear infinite;
- }
-}
-
-@keyframes spinning {
- to {
- transform: rotate(360deg);
- }
-}
-
-/* Custom TODO list checkboxes – shoutout to this awesome tutorial: https://moderncss.dev/pure-css-custom-checkbox-style/ */
-
-ul[data-type="taskList"] li > label {
- margin-right: 0.2rem;
- user-select: none;
-}
-
-@media screen and (max-width: 768px) {
- ul[data-type="taskList"] li > label {
- margin-right: 0.5rem;
- }
-}
-
-ul[data-type="taskList"] li > label input[type="checkbox"] {
- -webkit-appearance: none;
- appearance: none;
- background-color: hsl(var(--background));
- margin: 0;
- cursor: pointer;
- width: 1.2em;
- height: 1.2em;
- position: relative;
- top: 5px;
- border: 2px solid hsl(var(--border));
- margin-right: 0.3rem;
- display: grid;
- place-content: center;
-
- &:hover {
- background-color: hsl(var(--accent));
- }
-
- &:active {
- background-color: hsl(var(--accent));
- }
-
- &::before {
- content: "";
- width: 0.65em;
- height: 0.65em;
- transform: scale(0);
- transition: 120ms transform ease-in-out;
- box-shadow: inset 1em 1em;
- transform-origin: center;
- clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
- }
-
- &:checked::before {
- transform: scale(1);
- }
-}
-
-ul[data-type="taskList"] li[data-checked="true"] > div > p {
- color: var(--muted-foreground);
- text-decoration: line-through;
- text-decoration-thickness: 2px;
-}
-
-/* Overwrite tippy-box original max-width */
-
-.tippy-box {
- max-width: 400px !important;
-}
-
-.ProseMirror:not(.dragging) .ProseMirror-selectednode {
- outline: none !important;
- background-color: var(--novel-highlight-blue);
- transition: background-color 0.2s;
- box-shadow: none;
-}
-
-.drag-handle {
- position: fixed;
- opacity: 1;
- transition: opacity ease-in 0.2s;
- border-radius: 0.25rem;
-
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' style='fill: rgba(0, 0, 0, 0.5)'%3E%3Cpath d='M3,2 C2.44771525,2 2,1.55228475 2,1 C2,0.44771525 2.44771525,0 3,0 C3.55228475,0 4,0.44771525 4,1 C4,1.55228475 3.55228475,2 3,2 Z M3,6 C2.44771525,6 2,5.55228475 2,5 C2,4.44771525 2.44771525,4 3,4 C3.55228475,4 4,4.44771525 4,5 C4,5.55228475 3.55228475,6 3,6 Z M3,10 C2.44771525,10 2,9.55228475 2,9 C2,8.44771525 2.44771525,8 3,8 C3.55228475,8 4,8.44771525 4,9 C4,9.55228475 3.55228475,10 3,10 Z M7,2 C6.44771525,2 6,1.55228475 6,1 C6,0.44771525 6.44771525,0 7,0 C7.55228475,0 8,0.44771525 8,1 C8,1.55228475 7.55228475,2 7,2 Z M7,6 C6.44771525,6 6,5.55228475 6,5 C6,4.44771525 6.44771525,4 7,4 C7.55228475,4 8,4.44771525 8,5 C8,5.55228475 7.55228475,6 7,6 Z M7,10 C6.44771525,10 6,9.55228475 6,9 C6,8.44771525 6.44771525,8 7,8 C7.55228475,8 8,8.44771525 8,9 C8,9.55228475 7.55228475,10 7,10 Z'%3E%3C/path%3E%3C/svg%3E");
- background-size: calc(0.5em + 0.375rem) calc(0.5em + 0.375rem);
- background-repeat: no-repeat;
- background-position: center;
- width: 1.2rem;
- height: 1.5rem;
- z-index: 50;
- cursor: grab;
-
- &:hover {
- background-color: var(--novel-stone-100);
- transition: background-color 0.2s;
- }
-
- &:active {
- background-color: var(--novel-stone-200);
- transition: background-color 0.2s;
- cursor: grabbing;
- }
-
- &.hide {
- opacity: 0;
- pointer-events: none;
- }
-
- @media screen and (max-width: 600px) {
- display: none;
- pointer-events: none;
- }
-}
-
-.dark .drag-handle {
- background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' style='fill: rgba(255, 255, 255, 0.5)'%3E%3Cpath d='M3,2 C2.44771525,2 2,1.55228475 2,1 C2,0.44771525 2.44771525,0 3,0 C3.55228475,0 4,0.44771525 4,1 C4,1.55228475 3.55228475,2 3,2 Z M3,6 C2.44771525,6 2,5.55228475 2,5 C2,4.44771525 2.44771525,4 3,4 C3.55228475,4 4,4.44771525 4,5 C4,5.55228475 3.55228475,6 3,6 Z M3,10 C2.44771525,10 2,9.55228475 2,9 C2,8.44771525 2.44771525,8 3,8 C3.55228475,8 4,8.44771525 4,9 C4,9.55228475 3.55228475,10 3,10 Z M7,2 C6.44771525,2 6,1.55228475 6,1 C6,0.44771525 6.44771525,0 7,0 C7.55228475,0 8,0.44771525 8,1 C8,1.55228475 7.55228475,2 7,2 Z M7,6 C6.44771525,6 6,5.55228475 6,5 C6,4.44771525 6.44771525,4 7,4 C7.55228475,4 8,4.44771525 8,5 C8,5.55228475 7.55228475,6 7,6 Z M7,10 C6.44771525,10 6,9.55228475 6,9 C6,8.44771525 6.44771525,8 7,8 C7.55228475,8 8,8.44771525 8,9 C8,9.55228475 7.55228475,10 7,10 Z'%3E%3C/path%3E%3C/svg%3E");
-}
-
-/* Custom Youtube Video CSS */
-iframe {
- border: 8px solid #ffd00027;
- border-radius: 4px;
- min-width: 200px;
- min-height: 200px;
- display: block;
- outline: 0px solid transparent;
-}
-
-div[data-youtube-video] > iframe {
- cursor: move;
- aspect-ratio: 16 / 9;
- width: 100%;
-}
-
-.ProseMirror-selectednode iframe {
- transition: outline 0.15s;
- outline: 6px solid #fbbf24;
-}
-
-@media only screen and (max-width: 480px) {
- div[data-youtube-video] > iframe {
- max-height: 50px;
- }
-}
-
-@media only screen and (max-width: 720px) {
- div[data-youtube-video] > iframe {
- max-height: 100px;
- }
-} \ No newline at end of file
diff --git a/apps/web/public/logo.svg b/apps/web/public/logo.svg
index 6081634d..add59158 100644
--- a/apps/web/public/logo.svg
+++ b/apps/web/public/logo.svg
@@ -1,4 +1,4 @@
<svg width="42" height="42" viewBox="0 0 42 42" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.0357 8C20.5531 8 21 9.27461 21 10.8438V16.3281H23.5536V14.2212C23.5536 13.1976 23.9468 12.216 24.6467 11.4922L25.0529 11.0721C24.9729 10.8772 24.9286 10.6627 24.9286 10.4375C24.9286 9.54004 25.6321 8.8125 26.5 8.8125C27.3679 8.8125 28.0714 9.54004 28.0714 10.4375C28.0714 11.335 27.3679 12.0625 26.5 12.0625C26.2822 12.0625 26.0748 12.0167 25.8863 11.9339L25.4801 12.354C25.0012 12.8492 24.7321 13.5209 24.7321 14.2212V16.3281H28.9714C29.2045 15.7326 29.7691 15.3125 30.4286 15.3125C31.2964 15.3125 32 16.04 32 16.9375C32 17.835 31.2964 18.5625 30.4286 18.5625C29.7691 18.5625 29.2045 18.1424 28.9714 17.5469H21V21.2031H25.0428C25.2759 20.6076 25.8405 20.1875 26.5 20.1875C27.3679 20.1875 28.0714 20.915 28.0714 21.8125C28.0714 22.71 27.3679 23.4375 26.5 23.4375C25.8405 23.4375 25.2759 23.0174 25.0428 22.4219H21V26.0781H24.4125C25.4023 26.0781 26.3516 26.4847 27.0515 27.2085L29.0292 29.2536C29.2177 29.1708 29.4251 29.125 29.6429 29.125C30.5107 29.125 31.2143 29.8525 31.2143 30.75C31.2143 31.6475 30.5107 32.375 29.6429 32.375C28.775 32.375 28.0714 31.6475 28.0714 30.75C28.0714 30.5248 28.1157 30.3103 28.1958 30.1154L26.2181 28.0703C25.7392 27.5751 25.0897 27.2969 24.4125 27.2969H21V31.1562C21 32.7254 20.5531 34 19.0357 34C17.6165 34 16.4478 32.8879 16.3004 31.4559C16.0451 31.527 15.775 31.5625 15.5 31.5625C13.7665 31.5625 12.3571 30.1051 12.3571 28.3125C12.3571 27.9367 12.421 27.5711 12.5339 27.2359C11.0509 26.657 10 25.1742 10 23.4375C10 21.8176 10.9183 20.416 12.2491 19.766C11.8219 19.2125 11.5714 18.5117 11.5714 17.75C11.5714 16.191 12.6321 14.891 14.0464 14.5711C13.9679 14.2918 13.9286 13.9922 13.9286 13.6875C13.9286 12.1691 14.9402 10.8895 16.3004 10.534C16.4478 9.11211 17.6165 8 19.0357 8Z"
- fill="#fff"/>
+ fill="#545B62"/>
</svg> \ No newline at end of file
diff --git a/package.json b/package.json
index 7ceb7192..35b90102 100644
--- a/package.json
+++ b/package.json
@@ -47,6 +47,7 @@
"@aws-sdk/s3-request-presigner": "^3.577.0",
"@cloudflare/puppeteer": "^0.0.11",
"@headlessui/react": "^2.0.4",
+ "@heroicons/react": "^2.1.4",
"@hono/swagger-ui": "^0.2.2",
"@hookform/resolvers": "^3.4.2",
"@iarna/toml": "^2.2.5",
@@ -55,11 +56,13 @@
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
+ "@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
+ "@radix-ui/react-switch": "^1.1.0",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toast": "^1.1.5",
"@tldraw/assets": "^2.2.0",
diff --git a/packages/tailwind-config/globals.css b/packages/tailwind-config/globals.css
index a77eacbb..d09b120b 100644
--- a/packages/tailwind-config/globals.css
+++ b/packages/tailwind-config/globals.css
@@ -205,3 +205,19 @@ body {
.hljs-title {
color: #f22c3d;
}
+
+/* no scrollbar visibility */
+.no-scrollbar::-webkit-scrollbar {
+ width: 0px;
+}
+
+::-moz-selection {
+ /* Code for Firefox */
+ color: #369dfd;
+ background: #21303d;
+}
+
+::selection {
+ color: #369dfd;
+ background: #21303d;
+}
diff --git a/packages/ui/components/QueryInput.tsx b/packages/ui/components/QueryInput.tsx
new file mode 100644
index 00000000..ba476dda
--- /dev/null
+++ b/packages/ui/components/QueryInput.tsx
@@ -0,0 +1,60 @@
+import React from "react";
+import Divider from "../shadcn/divider";
+import { ArrowRightIcon } from "../icons";
+import Image from "next/image";
+
+function QueryInput() {
+ return (
+ <div>
+ <div className="bg-secondary rounded-[20px] h-[68 px]">
+ {/* input and action button */}
+ <form className="flex gap-4 p-2.5">
+ <textarea
+ name="q"
+ cols={30}
+ rows={4}
+ className="bg-transparent h-12 focus:h-[128px] no-scrollbar pt-3 px-2 text-base placeholder:text-[#5D6165] text-[#9DA0A4] focus:text-white duration-200 tracking-[3%] outline-none resize-none w-full"
+ placeholder="Ask your second brain..."
+ // onKeyDown={(e) => {
+ // if (e.key === "Enter") {
+ // e.preventDefault();
+ // if (!e.shiftKey) push(parseQ());
+ // }
+ // }}
+ // onChange={(e) => setQ(e.target.value)}
+ // value={q}
+ // disabled={disabled}
+ />
+
+ <button
+ // type="submit"
+ // onClick={e => e.preventDefault()}
+ // disabled={disabled}
+ className="h-12 w-12 rounded-[14px] bg-[#21303D] 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>
+
+ {/* <Divider /> */}
+ </div>
+ {/* selected sources */}
+ {/* <div className="flex items-center gap-6 p-2 h-auto bg-secondary"> */}
+ {/* <MultipleSelector
+ key={options.length}
+ disabled={disabled}
+ defaultOptions={options}
+ onChange={(e) => setSelectedSpaces(e.map((x) => parseInt(x.value)))}
+ placeholder="Focus on specific spaces..."
+ emptyIndicator={
+ <p className="text-center text-lg leading-10 text-gray-600 dark:text-gray-400">
+ no results found.
+ </p>
+ }
+ /> */}
+ {/* </div> */}
+ </div>
+ );
+}
+
+export default QueryInput;
diff --git a/packages/ui/components/canvas/draggableComponent.tsx b/packages/ui/components/canvas/draggableComponent.tsx
new file mode 100644
index 00000000..d0832e81
--- /dev/null
+++ b/packages/ui/components/canvas/draggableComponent.tsx
@@ -0,0 +1,71 @@
+import Image from "next/image";
+import { useRef, useState } from "react";
+
+interface DraggableComponentsProps {
+ content: string;
+ extraInfo?: string;
+ icon: string;
+ iconAlt: string;
+}
+
+export default function DraggableComponentsContainer({
+ content,
+}: {
+ content: DraggableComponentsProps[];
+}) {
+ return (
+ <div className="flex flex-col gap-10">
+ {content.map((i) => {
+ return (
+ <DraggableComponents
+ content={i.content}
+ icon={i.icon}
+ iconAlt={i.iconAlt}
+ extraInfo={i.extraInfo}
+ />
+ );
+ })}
+ </div>
+ );
+}
+
+function DraggableComponents({
+ content,
+ extraInfo,
+ icon,
+ iconAlt,
+}: DraggableComponentsProps) {
+ const [isDragging, setIsDragging] = useState(false);
+ const containerRef = useRef<HTMLDivElement>(null);
+
+ const handleDragStart = (event: React.DragEvent<HTMLDivElement>) => {
+ setIsDragging(true);
+ if (containerRef.current) {
+ // Serialize the children as a string for dataTransfer
+ const childrenHtml = containerRef.current.innerHTML;
+ event.dataTransfer.setData("text/html", childrenHtml);
+ }
+ };
+
+ const handleDragEnd = () => {
+ setIsDragging(false);
+ };
+
+ return (
+ <div
+ ref={containerRef}
+ onDragEnd={handleDragEnd}
+ onDragStart={handleDragStart}
+ draggable
+ className={`flex gap-4 px-1 rounded-md text-[#989EA4] border-2 transition ${isDragging ? "border-blue-600" : "border-[#1F2428]"}`}
+ >
+ <Image className="select-none" src={icon} alt={iconAlt} />
+ <div className="flex flex-col gap-2">
+ <div>
+ <h1 className="line-clamp-3">{content}</h1>
+ </div>
+ <p className="line-clamp-1 text-[#369DFD]">{extraInfo}</p>
+ </div>
+ </div>
+ );
+}
diff --git a/packages/ui/icons/add.svg b/packages/ui/icons/add.svg
index 1c6d87f6..970a7c95 100644
--- a/packages/ui/icons/add.svg
+++ b/packages/ui/icons/add.svg
@@ -1,3 +1,3 @@
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M10 4.25V16.75M16.25 10.5H3.75" stroke="#C2C7CB" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M10 4.25V16.75M16.25 10.5H3.75" stroke="#989EA4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg> \ No newline at end of file
diff --git a/packages/ui/icons/autocomplete.svg b/packages/ui/icons/autocomplete.svg
new file mode 100644
index 00000000..a4452242
--- /dev/null
+++ b/packages/ui/icons/autocomplete.svg
@@ -0,0 +1,15 @@
+ <svg
+ width="34"
+ height="24"
+ viewBox="0 0 14 10"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M0.8125 1.0625H13.1875M0.8125 5H13.1875M0.8125 8.9375H7"
+ stroke="#989EA4"
+ stroke-width="1.5"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ />
+ </svg> \ No newline at end of file
diff --git a/packages/ui/icons/block.svg b/packages/ui/icons/block.svg
new file mode 100644
index 00000000..ca4cc20e
--- /dev/null
+++ b/packages/ui/icons/block.svg
@@ -0,0 +1,15 @@
+ <svg
+ width="32"
+ height="36"
+ viewBox="0 0 16 18"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M14.75 5.625L8 1.6875L1.25 5.625M14.75 5.625L8 9.5625M14.75 5.625V12.375L8 16.3125M1.25 5.625L8 9.5625M1.25 5.625V12.375L8 16.3125M8 9.5625V16.3125"
+ stroke="#989EA4"
+ stroke-width="1.5"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ />
+ </svg> \ No newline at end of file
diff --git a/packages/ui/icons/canvas.svg b/packages/ui/icons/canvas.svg
new file mode 100644
index 00000000..529c75de
--- /dev/null
+++ b/packages/ui/icons/canvas.svg
@@ -0,0 +1,3 @@
+<svg width="22" height="20" viewBox="0 0 22 20" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M1.25 0.25C1.05109 0.25 0.860322 0.329018 0.71967 0.46967C0.579018 0.610322 0.5 0.801088 0.5 1C0.5 1.19891 0.579018 1.38968 0.71967 1.53033C0.860322 1.67098 1.05109 1.75 1.25 1.75H2V12.25C2 13.0456 2.31607 13.8087 2.87868 14.3713C3.44129 14.9339 4.20435 15.25 5 15.25H6.21L5.038 18.763C4.97514 18.9518 4.98988 19.1579 5.07896 19.3359C5.16804 19.5138 5.32417 19.6491 5.513 19.712C5.70183 19.7749 5.9079 19.7601 6.08588 19.671C6.26385 19.582 6.39914 19.4258 6.462 19.237L6.791 18.25H15.209L15.539 19.237C15.6073 19.4186 15.7433 19.5666 15.9184 19.6501C16.0935 19.7335 16.2941 19.7459 16.4782 19.6845C16.6622 19.6232 16.8153 19.4929 16.9053 19.3211C16.9954 19.1493 17.0153 18.9492 16.961 18.763L15.791 15.25H17C17.7956 15.25 18.5587 14.9339 19.1213 14.3713C19.6839 13.8087 20 13.0456 20 12.25V1.75H20.75C20.9489 1.75 21.1397 1.67098 21.2803 1.53033C21.421 1.38968 21.5 1.19891 21.5 1C21.5 0.801088 21.421 0.610322 21.2803 0.46967C21.1397 0.329018 20.9489 0.25 20.75 0.25H1.25ZM7.79 15.25H14.21L14.71 16.75H7.29L7.79 15.25ZM15.875 6.255C15.961 6.20611 16.0364 6.1407 16.097 6.06253C16.1577 5.98436 16.2022 5.89497 16.2281 5.79952C16.254 5.70406 16.2608 5.60443 16.2481 5.50634C16.2353 5.40826 16.2033 5.31366 16.1538 5.228C16.1044 5.14235 16.0385 5.06732 15.9599 5.00724C15.8813 4.94715 15.7916 4.90321 15.696 4.87793C15.6004 4.85264 15.5007 4.84653 15.4027 4.85993C15.3047 4.87333 15.2103 4.90598 15.125 4.956C13.7615 5.74523 12.5554 6.7792 11.567 8.006L10.03 6.47C9.88937 6.32955 9.69875 6.25066 9.5 6.25066C9.30125 6.25066 9.11063 6.32955 8.97 6.47L5.97 9.47C5.89631 9.53866 5.83721 9.62146 5.79622 9.71346C5.75523 9.80546 5.73319 9.90477 5.73141 10.0055C5.72963 10.1062 5.74816 10.2062 5.78588 10.2996C5.8236 10.393 5.87974 10.4778 5.95096 10.549C6.02218 10.6203 6.10701 10.6764 6.2004 10.7141C6.29379 10.7518 6.39382 10.7704 6.49452 10.7686C6.59523 10.7668 6.69454 10.7448 6.78654 10.7038C6.87854 10.6628 6.96134 10.6037 7.03 10.53L9.5 8.06L11.117 9.678C11.1946 9.75558 11.2882 9.81519 11.3913 9.85264C11.4945 9.89008 11.6046 9.90445 11.7138 9.89472C11.8231 9.885 11.9289 9.85142 12.0238 9.79635C12.1187 9.74128 12.2003 9.66606 12.263 9.576C13.2095 8.21806 14.4425 7.0844 15.875 6.255Z" fill="#6A737D"/>
+</svg>
diff --git a/packages/ui/icons/chat.svg b/packages/ui/icons/chat.svg
index 7fe96c16..fd33dcd7 100644
--- a/packages/ui/icons/chat.svg
+++ b/packages/ui/icons/chat.svg
@@ -1,3 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M7.1875 10C7.1875 10.0829 7.15458 10.1624 7.09597 10.221C7.03737 10.2796 6.95788 10.3125 6.875 10.3125C6.79212 10.3125 6.71263 10.2796 6.65403 10.221C6.59542 10.1624 6.5625 10.0829 6.5625 10C6.5625 9.91712 6.59542 9.83763 6.65403 9.77903C6.71263 9.72042 6.79212 9.6875 6.875 9.6875C6.95788 9.6875 7.03737 9.72042 7.09597 9.77903C7.15458 9.83763 7.1875 9.91712 7.1875 10ZM7.1875 10H6.875M10.3125 10C10.3125 10.0829 10.2796 10.1624 10.221 10.221C10.1624 10.2796 10.0829 10.3125 10 10.3125C9.91712 10.3125 9.83763 10.2796 9.77903 10.221C9.72042 10.1624 9.6875 10.0829 9.6875 10C9.6875 9.91712 9.72042 9.83763 9.77903 9.77903C9.83763 9.72042 9.91712 9.6875 10 9.6875C10.0829 9.6875 10.1624 9.72042 10.221 9.77903C10.2796 9.83763 10.3125 9.91712 10.3125 10ZM10.3125 10H10M13.4375 10C13.4375 10.0829 13.4046 10.1624 13.346 10.221C13.2874 10.2796 13.2079 10.3125 13.125 10.3125C13.0421 10.3125 12.9626 10.2796 12.904 10.221C12.8454 10.1624 12.8125 10.0829 12.8125 10C12.8125 9.91712 12.8454 9.83763 12.904 9.77903C12.9626 9.72042 13.0421 9.6875 13.125 9.6875C13.2079 9.6875 13.2874 9.72042 13.346 9.77903C13.4046 9.83763 13.4375 9.91712 13.4375 10ZM13.4375 10H13.125M17.5 10C17.5 13.7967 14.1417 16.875 10 16.875C9.28099 16.8759 8.56503 16.7814 7.87083 16.5942C6.8923 17.2824 5.6986 17.5951 4.50833 17.475C4.376 17.4622 4.24422 17.4442 4.11333 17.4208C4.52406 16.9368 4.80456 16.356 4.92833 15.7333C5.00333 15.3525 4.8175 14.9825 4.53917 14.7117C3.275 13.4817 2.5 11.8242 2.5 10C2.5 6.20333 5.85833 3.125 10 3.125C14.1417 3.125 17.5 6.20333 17.5 10Z" stroke="#989EA4" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M7.1875 10C7.1875 10.0829 7.15458 10.1624 7.09597 10.221C7.03737 10.2796 6.95788 10.3125 6.875 10.3125C6.79212 10.3125 6.71263 10.2796 6.65403 10.221C6.59542 10.1624 6.5625 10.0829 6.5625 10C6.5625 9.91712 6.59542 9.83763 6.65403 9.77903C6.71263 9.72042 6.79212 9.6875 6.875 9.6875C6.95788 9.6875 7.03737 9.72042 7.09597 9.77903C7.15458 9.83763 7.1875 9.91712 7.1875 10ZM7.1875 10H6.875M10.3125 10C10.3125 10.0829 10.2796 10.1624 10.221 10.221C10.1624 10.2796 10.0829 10.3125 10 10.3125C9.91712 10.3125 9.83763 10.2796 9.77903 10.221C9.72042 10.1624 9.6875 10.0829 9.6875 10C9.6875 9.91712 9.72042 9.83763 9.77903 9.77903C9.83763 9.72042 9.91712 9.6875 10 9.6875C10.0829 9.6875 10.1624 9.72042 10.221 9.77903C10.2796 9.83763 10.3125 9.91712 10.3125 10ZM10.3125 10H10M13.4375 10C13.4375 10.0829 13.4046 10.1624 13.346 10.221C13.2874 10.2796 13.2079 10.3125 13.125 10.3125C13.0421 10.3125 12.9626 10.2796 12.904 10.221C12.8454 10.1624 12.8125 10.0829 12.8125 10C12.8125 9.91712 12.8454 9.83763 12.904 9.77903C12.9626 9.72042 13.0421 9.6875 13.125 9.6875C13.2079 9.6875 13.2874 9.72042 13.346 9.77903C13.4046 9.83763 13.4375 9.91712 13.4375 10ZM13.4375 10H13.125M17.5 10C17.5 13.7967 14.1417 16.875 10 16.875C9.28099 16.8759 8.56503 16.7814 7.87083 16.5942C6.8923 17.2824 5.6986 17.5951 4.50833 17.475C4.376 17.4622 4.24422 17.4442 4.11333 17.4208C4.52406 16.9368 4.80456 16.356 4.92833 15.7333C5.00333 15.3525 4.8175 14.9825 4.53917 14.7117C3.275 13.4817 2.5 11.8242 2.5 10C2.5 6.20333 5.85833 3.125 10 3.125C14.1417 3.125 17.5 6.20333 17.5 10Z" stroke="#7D8994" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg> \ No newline at end of file
diff --git a/apps/web/app/(canvas)/svg.tsx b/packages/ui/icons/drag.svg
index bae4e614..366a4cb0 100644
--- a/apps/web/app/(canvas)/svg.tsx
+++ b/packages/ui/icons/drag.svg
@@ -1,24 +1,3 @@
-export function SettingsSvg() {
- return (
- <svg
- width="16"
- height="18"
- viewBox="0 0 16 18"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- >
- <path
- fill-rule="evenodd"
- clip-rule="evenodd"
- d="M7.2321 0.875C6.46793 0.875 5.81627 1.4275 5.69043 2.18083L5.5421 3.07417C5.52543 3.17417 5.44627 3.29083 5.2946 3.36417C5.00906 3.50143 4.73438 3.66022 4.47293 3.83917C4.3346 3.935 4.1946 3.94417 4.09793 3.90833L3.25043 3.59C2.90397 3.4602 2.52268 3.45755 2.17445 3.58253C1.82621 3.70751 1.53362 3.95201 1.34877 4.2725L0.580434 5.60333C0.395508 5.92363 0.330196 6.29915 0.396115 6.66307C0.462034 7.027 0.65491 7.35575 0.940434 7.59083L1.64043 8.1675C1.7196 8.2325 1.7821 8.35833 1.76877 8.52583C1.74502 8.84178 1.74502 9.15906 1.76877 9.475C1.78127 9.64167 1.7196 9.76833 1.64127 9.83333L0.940434 10.41C0.65491 10.6451 0.462034 10.9738 0.396115 11.3378C0.330196 11.7017 0.395508 12.0772 0.580434 12.3975L1.34877 13.7283C1.53376 14.0487 1.8264 14.293 2.17462 14.4178C2.52285 14.5426 2.90406 14.5399 3.25043 14.41L4.0996 14.0917C4.19543 14.0558 4.33543 14.0658 4.4746 14.16C4.7346 14.3383 5.00877 14.4975 5.29543 14.635C5.4471 14.7083 5.52627 14.825 5.54293 14.9267L5.69127 15.8192C5.8171 16.5725 6.46877 17.125 7.23293 17.125H8.7696C9.53293 17.125 10.1854 16.5725 10.3113 15.8192L10.4596 14.9258C10.4763 14.8258 10.5546 14.7092 10.7071 14.635C10.9938 14.4975 11.2679 14.3383 11.5279 14.16C11.6671 14.065 11.8071 14.0558 11.9029 14.0917L12.7529 14.41C13.0992 14.5394 13.4801 14.5418 13.828 14.4168C14.1758 14.2919 14.4681 14.0476 14.6529 13.7275L15.4221 12.3967C15.607 12.0764 15.6723 11.7009 15.6064 11.3369C15.5405 10.973 15.3476 10.6443 15.0621 10.4092L14.3621 9.8325C14.2829 9.7675 14.2204 9.64167 14.2338 9.47417C14.2575 9.15822 14.2575 8.84095 14.2338 8.525C14.2204 8.35833 14.2829 8.23167 14.3613 8.16667L15.0613 7.59C15.6513 7.105 15.8038 6.265 15.4221 5.6025L14.6538 4.27167C14.4688 3.95132 14.1761 3.707 13.8279 3.58218C13.4797 3.45735 13.0985 3.46013 12.7521 3.59L11.9021 3.90833C11.8071 3.94417 11.6671 3.93417 11.5279 3.83917C11.2668 3.66025 10.9924 3.50145 10.7071 3.36417C10.5546 3.29167 10.4763 3.175 10.4596 3.07417L10.3104 2.18083C10.2497 1.81589 10.0614 1.48435 9.77905 1.24522C9.49674 1.0061 9.13874 0.874907 8.76877 0.875H7.23293H7.2321ZM8.00043 12.125C8.82923 12.125 9.62409 11.7958 10.2101 11.2097C10.7962 10.6237 11.1254 9.8288 11.1254 9C11.1254 8.1712 10.7962 7.37634 10.2101 6.79029C9.62409 6.20424 8.82923 5.875 8.00043 5.875C7.17163 5.875 6.37678 6.20424 5.79072 6.79029C5.20467 7.37634 4.87543 8.1712 4.87543 9C4.87543 9.8288 5.20467 10.6237 5.79072 11.2097C6.37678 11.7958 7.17163 12.125 8.00043 12.125Z"
- fill="#989EA4"
- />
- </svg>
- );
-}
-
-export function DragSvg() {
- return (
<svg
width="6"
height="9"
@@ -32,66 +11,4 @@ export function DragSvg() {
d="M4.78829 0.916134C4.78348 0.920945 4.77696 0.923648 4.77015 0.923648C4.76335 0.923648 4.75682 0.920945 4.75201 0.916134C4.7472 0.911322 4.74449 0.904797 4.74449 0.897991C4.74449 0.891186 4.7472 0.884661 4.75201 0.879849C4.75682 0.875038 4.76335 0.872335 4.77015 0.872335C4.77696 0.872335 4.78348 0.875038 4.78829 0.879849C4.79311 0.884661 4.79581 0.891186 4.79581 0.897991C4.79581 0.904797 4.79311 0.911322 4.78829 0.916134ZM4.77015 0C4.53199 0 4.30358 0.0946096 4.13518 0.263016C3.96677 0.431421 3.87216 0.659829 3.87216 0.897991C3.87216 1.13615 3.96677 1.36456 4.13518 1.53297C4.30358 1.70137 4.53199 1.79598 4.77015 1.79598C5.00831 1.79598 5.23672 1.70137 5.40513 1.53297C5.57353 1.36456 5.66814 1.13615 5.66814 0.897991C5.66814 0.659829 5.57353 0.431421 5.40513 0.263016C5.23672 0.0946096 5.00831 0 4.77015 0ZM4.78829 4.40547C4.78348 4.41028 4.77696 4.41299 4.77015 4.41299C4.76335 4.41299 4.75682 4.41028 4.75201 4.40547C4.7472 4.40066 4.74449 4.39414 4.74449 4.38733C4.74449 4.38052 4.7472 4.374 4.75201 4.36919C4.75682 4.36438 4.76335 4.36167 4.77015 4.36167C4.77696 4.36167 4.78348 4.36438 4.78829 4.36919C4.79311 4.374 4.79581 4.38052 4.79581 4.38733C4.79581 4.39414 4.79311 4.40066 4.78829 4.40547ZM4.77015 3.48934C4.53199 3.48934 4.30358 3.58395 4.13518 3.75235C3.96677 3.92076 3.87216 4.14917 3.87216 4.38733C3.87216 4.62549 3.96677 4.8539 4.13518 5.02231C4.30358 5.19071 4.53199 5.28532 4.77015 5.28532C5.00831 5.28532 5.23672 5.19071 5.40513 5.02231C5.57353 4.8539 5.66814 4.62549 5.66814 4.38733C5.66814 4.14917 5.57353 3.92076 5.40513 3.75235C5.23672 3.58395 5.00831 3.48934 4.77015 3.48934ZM4.78829 7.89481C4.78348 7.89962 4.77696 7.90232 4.77015 7.90232C4.76335 7.90232 4.75682 7.89962 4.75201 7.89481C4.7472 7.89 4.74449 7.88347 4.74449 7.87667C4.74449 7.86986 4.7472 7.86334 4.75201 7.85853C4.75682 7.85371 4.76335 7.85101 4.77015 7.85101C4.77696 7.85101 4.78348 7.85371 4.78829 7.85853C4.79311 7.86334 4.79581 7.86986 4.79581 7.87667C4.79581 7.88347 4.79311 7.89 4.78829 7.89481ZM4.77015 6.97868C4.53199 6.97868 4.30358 7.07329 4.13518 7.24169C3.96677 7.4101 3.87216 7.63851 3.87216 7.87667C3.87216 8.11483 3.96677 8.34324 4.13518 8.51164C4.30358 8.68005 4.53199 8.77466 4.77015 8.77466C5.00831 8.77466 5.23672 8.68005 5.40513 8.51164C5.57353 8.34324 5.66814 8.11483 5.66814 7.87667C5.66814 7.63851 5.57353 7.4101 5.40513 7.24169C5.23672 7.07329 5.00831 6.97868 4.77015 6.97868ZM0.916134 0.91702C0.911322 0.921832 0.904796 0.924535 0.897991 0.924535C0.891187 0.924535 0.884661 0.921832 0.879849 0.91702C0.875038 0.912209 0.872335 0.905683 0.872335 0.898878C0.872335 0.892073 0.875038 0.885547 0.879849 0.880736C0.884661 0.875924 0.891187 0.873221 0.897991 0.873221C0.904796 0.873221 0.911322 0.875924 0.916134 0.880736C0.920945 0.885547 0.923648 0.892073 0.923648 0.898878C0.923648 0.905683 0.920945 0.912209 0.916134 0.91702ZM0.897991 0.000886679C0.659829 0.000886679 0.431422 0.0954962 0.263016 0.263902C0.0946102 0.432308 0 0.660715 0 0.898878C0 1.13704 0.0946102 1.36545 0.263016 1.53385C0.431422 1.70226 0.659829 1.79687 0.897991 1.79687C1.13615 1.79687 1.36456 1.70226 1.53297 1.53385C1.70137 1.36545 1.79598 1.13704 1.79598 0.898878C1.79598 0.660715 1.70137 0.432308 1.53297 0.263902C1.36456 0.0954962 1.13615 0.000886679 0.897991 0.000886679ZM0.916134 4.40636C0.911323 4.41117 0.904797 4.41387 0.897991 4.41387C0.891186 4.41387 0.88466 4.41117 0.879849 4.40636C0.875038 4.40155 0.872335 4.39502 0.872335 4.38822C0.872335 4.38141 0.875038 4.37489 0.879849 4.37007C0.88466 4.36526 0.891186 4.36256 0.897991 4.36256C0.904797 4.36256 0.911323 4.36526 0.916134 4.37007C0.920945 4.37489 0.923648 4.38141 0.923648 4.38822C0.923648 4.39502 0.920945 4.40155 0.916134 4.40636ZM0.897991 3.49022C0.659828 3.49022 0.431421 3.58484 0.263016 3.75324C0.0946104 3.92165 0 4.15005 0 4.38822C0 4.62638 0.0946104 4.85479 0.263016 5.02319C0.431421 5.1916 0.659828 5.28621 0.897991 5.28621C1.13615 5.28621 1.36456 5.1916 1.53297 5.02319C1.70137 4.85479 1.79598 4.62638 1.79598 4.38822C1.79598 4.15005 1.70137 3.92165 1.53297 3.75324C1.36456 3.58484 1.13615 3.49022 0.897991 3.49022ZM0.916134 7.8957C0.911322 7.90051 0.904795 7.90321 0.897991 7.90321C0.891187 7.90321 0.884661 7.90051 0.879849 7.8957C0.875038 7.89089 0.872335 7.88436 0.872335 7.87755C0.872335 7.87075 0.875038 7.86422 0.879849 7.85941C0.884661 7.8546 0.891187 7.8519 0.897991 7.8519C0.904795 7.8519 0.911322 7.8546 0.916134 7.85941C0.920945 7.86422 0.923648 7.87075 0.923648 7.87755C0.923648 7.88436 0.920945 7.89089 0.916134 7.8957ZM0.897991 6.97956C0.65983 6.97956 0.431422 7.07417 0.263016 7.24258C0.0946099 7.41098 0 7.63939 0 7.87755C0 8.11572 0.0946099 8.34412 0.263016 8.51253C0.431422 8.68094 0.65983 8.77555 0.897991 8.77555C1.13615 8.77555 1.36456 8.68094 1.53297 8.51253C1.70137 8.34412 1.79598 8.11572 1.79598 7.87755C1.79598 7.63939 1.70137 7.41098 1.53297 7.24258C1.36456 7.07417 1.13615 6.97956 0.897991 6.97956Z"
fill="#989EA4"
/>
- </svg>
- );
-}
-
-export function TextLoadingSvg() {
- return (
- <svg
- width="34"
- height="24"
- viewBox="0 0 14 10"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- >
- <path
- d="M0.8125 1.0625H13.1875M0.8125 5H13.1875M0.8125 8.9375H7"
- stroke="#989EA4"
- stroke-width="1.5"
- stroke-linecap="round"
- stroke-linejoin="round"
- />
- </svg>
- );
-}
-
-export function ThreeDBlock() {
- return (
- <svg
- width="32"
- height="36"
- viewBox="0 0 16 18"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- >
- <path
- d="M14.75 5.625L8 1.6875L1.25 5.625M14.75 5.625L8 9.5625M14.75 5.625V12.375L8 16.3125M1.25 5.625L8 9.5625M1.25 5.625V12.375L8 16.3125M8 9.5625V16.3125"
- stroke="#989EA4"
- stroke-width="1.5"
- stroke-linecap="round"
- stroke-linejoin="round"
- />
- </svg>
- );
-}
-
-export function LinkSvg() {
- return (
- <svg
- width="36"
- height="36"
- viewBox="0 0 18 18"
- fill="none"
- xmlns="http://www.w3.org/2000/svg"
- >
- <path
- d="M9.89252 6.51602C10.3799 6.74871 10.8043 7.09497 11.1301 7.5257C11.4559 7.95643 11.6736 8.45906 11.7648 8.99136C11.8561 9.52366 11.8183 10.0701 11.6546 10.5848C11.4909 11.0994 11.206 11.5673 10.824 11.949L7.44902 15.324C6.81608 15.957 5.95763 16.3125 5.06252 16.3125C4.16741 16.3125 3.30896 15.957 2.67602 15.324C2.04308 14.6911 1.6875 13.8326 1.6875 12.9375C1.6875 12.0424 2.04308 11.184 2.67602 10.551L3.99377 9.23327M14.0063 8.76677L15.324 7.44902C15.957 6.81608 16.3125 5.95763 16.3125 5.06252C16.3125 4.16741 15.957 3.30896 15.324 2.67602C14.6911 2.04308 13.8326 1.6875 12.9375 1.6875C12.0424 1.6875 11.184 2.04308 10.551 2.67602L7.17602 6.05102C6.794 6.43277 6.50917 6.90063 6.34546 7.41529C6.18175 7.92995 6.14393 8.47638 6.2352 9.00868C6.32646 9.54098 6.54414 10.0436 6.86994 10.4743C7.19574 10.9051 7.62015 11.2513 8.10752 11.484"
- stroke="#989EA4"
- stroke-width="1.5"
- stroke-linecap="round"
- stroke-linejoin="round"
- />
- </svg>
- );
-}
+ </svg> \ No newline at end of file
diff --git a/packages/ui/icons/explore.svg b/packages/ui/icons/explore.svg
index d6332612..98785c14 100644
--- a/packages/ui/icons/explore.svg
+++ b/packages/ui/icons/explore.svg
@@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M6.5 17.5L14 14L17.5 6.5L10 10L6.5 17.5ZM12 13C11.7167 13 11.4793 12.904 11.288 12.712C11.0967 12.52 11.0007 12.2827 11 12C10.9993 11.7173 11.0953 11.48 11.288 11.288C11.4807 11.096 11.718 11 12 11C12.282 11 12.5197 11.096 12.713 11.288C12.9063 11.48 13.002 11.7173 13 12C12.998 12.2827 12.902 12.5203 12.712 12.713C12.522 12.9057 12.2847 13.0013 12 13ZM12 22C10.6167 22 9.31667 21.7373 8.1 21.212C6.88334 20.6867 5.825 19.9743 4.925 19.075C4.025 18.1757 3.31267 17.1173 2.788 15.9C2.26333 14.6827 2.00067 13.3827 2 12C1.99933 10.6173 2.262 9.31733 2.788 8.1C3.314 6.88267 4.02633 5.82433 4.925 4.925C5.82367 4.02567 6.882 3.31333 8.1 2.788C9.318 2.26267 10.618 2 12 2C13.382 2 14.682 2.26267 15.9 2.788C17.118 3.31333 18.1763 4.02567 19.075 4.925C19.9737 5.82433 20.6863 6.88267 21.213 8.1C21.7397 9.31733 22.002 10.6173 22 12C21.998 13.3827 21.7353 14.6827 21.212 15.9C20.6887 17.1173 19.9763 18.1757 19.075 19.075C18.1737 19.9743 17.1153 20.687 15.9 21.213C14.6847 21.739 13.3847 22.0013 12 22Z" fill="#6A737D"/>
+<path d="M6.5 17.5L14 14L17.5 6.5L10 10L6.5 17.5ZM12 13C11.7167 13 11.4793 12.904 11.288 12.712C11.0967 12.52 11.0007 12.2827 11 12C10.9993 11.7173 11.0953 11.48 11.288 11.288C11.4807 11.096 11.718 11 12 11C12.282 11 12.5197 11.096 12.713 11.288C12.9063 11.48 13.002 11.7173 13 12C12.998 12.2827 12.902 12.5203 12.712 12.713C12.522 12.9057 12.2847 13.0013 12 13ZM12 22C10.6167 22 9.31667 21.7373 8.1 21.212C6.88334 20.6867 5.825 19.9743 4.925 19.075C4.025 18.1757 3.31267 17.1173 2.788 15.9C2.26333 14.6827 2.00067 13.3827 2 12C1.99933 10.6173 2.262 9.31733 2.788 8.1C3.314 6.88267 4.02633 5.82433 4.925 4.925C5.82367 4.02567 6.882 3.31333 8.1 2.788C9.318 2.26267 10.618 2 12 2C13.382 2 14.682 2.26267 15.9 2.788C17.118 3.31333 18.1763 4.02567 19.075 4.925C19.9737 5.82433 20.6863 6.88267 21.213 8.1C21.7397 9.31733 22.002 10.6173 22 12C21.998 13.3827 21.7353 14.6827 21.212 15.9C20.6887 17.1173 19.9763 18.1757 19.075 19.075C18.1737 19.9743 17.1153 20.687 15.9 21.213C14.6847 21.739 13.3847 22.0013 12 22Z" fill="#777E87"/>
</svg> \ No newline at end of file
diff --git a/packages/ui/icons/history.svg b/packages/ui/icons/history.svg
index 7766c75d..9d735d5a 100644
--- a/packages/ui/icons/history.svg
+++ b/packages/ui/icons/history.svg
@@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2.25C6.615 2.25 2.25 6.615 2.25 12C2.25 17.385 6.615 21.75 12 21.75C17.385 21.75 21.75 17.385 21.75 12C21.75 6.615 17.385 2.25 12 2.25ZM12.75 6C12.75 5.80109 12.671 5.61032 12.5303 5.46967C12.3897 5.32902 12.1989 5.25 12 5.25C11.8011 5.25 11.6103 5.32902 11.4697 5.46967C11.329 5.61032 11.25 5.80109 11.25 6V12C11.25 12.414 11.586 12.75 12 12.75H16.5C16.6989 12.75 16.8897 12.671 17.0303 12.5303C17.171 12.3897 17.25 12.1989 17.25 12C17.25 11.8011 17.171 11.6103 17.0303 11.4697C16.8897 11.329 16.6989 11.25 16.5 11.25H12.75V6Z" fill="#6A737D"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2.25C6.615 2.25 2.25 6.615 2.25 12C2.25 17.385 6.615 21.75 12 21.75C17.385 21.75 21.75 17.385 21.75 12C21.75 6.615 17.385 2.25 12 2.25ZM12.75 6C12.75 5.80109 12.671 5.61032 12.5303 5.46967C12.3897 5.32902 12.1989 5.25 12 5.25C11.8011 5.25 11.6103 5.32902 11.4697 5.46967C11.329 5.61032 11.25 5.80109 11.25 6V12C11.25 12.414 11.586 12.75 12 12.75H16.5C16.6989 12.75 16.8897 12.671 17.0303 12.5303C17.171 12.3897 17.25 12.1989 17.25 12C17.25 11.8011 17.171 11.6103 17.0303 11.4697C16.8897 11.329 16.6989 11.25 16.5 11.25H12.75V6Z" fill="#777E87"/>
</svg> \ No newline at end of file
diff --git a/packages/ui/icons/index.ts b/packages/ui/icons/index.ts
index 7788c20f..cf2f0aaa 100644
--- a/packages/ui/icons/index.ts
+++ b/packages/ui/icons/index.ts
@@ -8,6 +8,13 @@ import SelectIcon from "./select.svg";
import SearchIcon from "./search.svg";
import NextIcon from "./nextarrow.svg";
import UrlIcon from "./url.svg";
+import CanvasIcon from "./canvas.svg";
+import blockIcon from "./block.svg";
+import LinkIcon from "./link.svg";
+import AutocompleteIcon from "./autocomplete.svg";
+import BlockIcon from "./block.svg";
+import DragIcon from "./drag.svg";
+import SettingsIcon from "./settings.svg";
export {
AddIcon,
@@ -19,5 +26,12 @@ export {
SelectIcon,
SearchIcon,
NextIcon,
- UrlIcon
+ UrlIcon,
+ CanvasIcon,
+ blockIcon,
+ LinkIcon,
+ AutocompleteIcon,
+ BlockIcon,
+ DragIcon,
+ SettingsIcon,
};
diff --git a/packages/ui/icons/link.svg b/packages/ui/icons/link.svg
new file mode 100644
index 00000000..25e2d04e
--- /dev/null
+++ b/packages/ui/icons/link.svg
@@ -0,0 +1,15 @@
+ <svg
+ width="36"
+ height="36"
+ viewBox="0 0 18 18"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="M9.89252 6.51602C10.3799 6.74871 10.8043 7.09497 11.1301 7.5257C11.4559 7.95643 11.6736 8.45906 11.7648 8.99136C11.8561 9.52366 11.8183 10.0701 11.6546 10.5848C11.4909 11.0994 11.206 11.5673 10.824 11.949L7.44902 15.324C6.81608 15.957 5.95763 16.3125 5.06252 16.3125C4.16741 16.3125 3.30896 15.957 2.67602 15.324C2.04308 14.6911 1.6875 13.8326 1.6875 12.9375C1.6875 12.0424 2.04308 11.184 2.67602 10.551L3.99377 9.23327M14.0063 8.76677L15.324 7.44902C15.957 6.81608 16.3125 5.95763 16.3125 5.06252C16.3125 4.16741 15.957 3.30896 15.324 2.67602C14.6911 2.04308 13.8326 1.6875 12.9375 1.6875C12.0424 1.6875 11.184 2.04308 10.551 2.67602L7.17602 6.05102C6.794 6.43277 6.50917 6.90063 6.34546 7.41529C6.18175 7.92995 6.14393 8.47638 6.2352 9.00868C6.32646 9.54098 6.54414 10.0436 6.86994 10.4743C7.19574 10.9051 7.62015 11.2513 8.10752 11.484"
+ stroke="#989EA4"
+ stroke-width="1.5"
+ stroke-linecap="round"
+ stroke-linejoin="round"
+ />
+ </svg> \ No newline at end of file
diff --git a/packages/ui/icons/memories.svg b/packages/ui/icons/memories.svg
index 27784aac..069b4db8 100644
--- a/packages/ui/icons/memories.svg
+++ b/packages/ui/icons/memories.svg
@@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-<path d="M5.566 4.657C5.95195 4.55235 6.35011 4.49955 6.75 4.5H17.25C17.66 4.5 18.056 4.555 18.433 4.657C18.1837 4.15898 17.8006 3.7402 17.3268 3.44754C16.8529 3.15489 16.3069 2.99993 15.75 3H8.25C7.69288 2.99974 7.1467 3.15462 6.67265 3.44728C6.1986 3.73994 5.8154 4.15883 5.566 4.657ZM2.25 12C2.25 11.2044 2.56607 10.4413 3.12868 9.87868C3.69129 9.31607 4.45435 9 5.25 9H18.75C19.5456 9 20.3087 9.31607 20.8713 9.87868C21.4339 10.4413 21.75 11.2044 21.75 12V18C21.75 18.7956 21.4339 19.5587 20.8713 20.1213C20.3087 20.6839 19.5456 21 18.75 21H5.25C4.45435 21 3.69129 20.6839 3.12868 20.1213C2.56607 19.5587 2.25 18.7956 2.25 18V12ZM5.25 7.5C4.84 7.5 4.444 7.555 4.066 7.657C4.3154 7.15883 4.6986 6.73994 5.17265 6.44728C5.6467 6.15462 6.19288 5.99974 6.75 6H17.25C17.8069 5.99993 18.3529 6.15489 18.8268 6.44754C19.3006 6.7402 19.6837 7.15898 19.933 7.657C19.5474 7.55244 19.1496 7.49964 18.75 7.5H5.25Z" fill="#6A737D"/>
+<path d="M5.566 4.657C5.95195 4.55235 6.35011 4.49955 6.75 4.5H17.25C17.66 4.5 18.056 4.555 18.433 4.657C18.1837 4.15898 17.8006 3.7402 17.3268 3.44754C16.8529 3.15489 16.3069 2.99993 15.75 3H8.25C7.69288 2.99974 7.1467 3.15462 6.67265 3.44728C6.1986 3.73994 5.8154 4.15883 5.566 4.657ZM2.25 12C2.25 11.2044 2.56607 10.4413 3.12868 9.87868C3.69129 9.31607 4.45435 9 5.25 9H18.75C19.5456 9 20.3087 9.31607 20.8713 9.87868C21.4339 10.4413 21.75 11.2044 21.75 12V18C21.75 18.7956 21.4339 19.5587 20.8713 20.1213C20.3087 20.6839 19.5456 21 18.75 21H5.25C4.45435 21 3.69129 20.6839 3.12868 20.1213C2.56607 19.5587 2.25 18.7956 2.25 18V12ZM5.25 7.5C4.84 7.5 4.444 7.555 4.066 7.657C4.3154 7.15883 4.6986 6.73994 5.17265 6.44728C5.6467 6.15462 6.19288 5.99974 6.75 6H17.25C17.8069 5.99993 18.3529 6.15489 18.8268 6.44754C19.3006 6.7402 19.6837 7.15898 19.933 7.657C19.5474 7.55244 19.1496 7.49964 18.75 7.5H5.25Z" fill="#777E87"/>
</svg> \ No newline at end of file
diff --git a/packages/ui/icons/settings.svg b/packages/ui/icons/settings.svg
new file mode 100644
index 00000000..2059b976
--- /dev/null
+++ b/packages/ui/icons/settings.svg
@@ -0,0 +1,14 @@
+ <svg
+ width="16"
+ height="18"
+ viewBox="0 0 16 18"
+ fill="none"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ fill-rule="evenodd"
+ clip-rule="evenodd"
+ d="M7.2321 0.875C6.46793 0.875 5.81627 1.4275 5.69043 2.18083L5.5421 3.07417C5.52543 3.17417 5.44627 3.29083 5.2946 3.36417C5.00906 3.50143 4.73438 3.66022 4.47293 3.83917C4.3346 3.935 4.1946 3.94417 4.09793 3.90833L3.25043 3.59C2.90397 3.4602 2.52268 3.45755 2.17445 3.58253C1.82621 3.70751 1.53362 3.95201 1.34877 4.2725L0.580434 5.60333C0.395508 5.92363 0.330196 6.29915 0.396115 6.66307C0.462034 7.027 0.65491 7.35575 0.940434 7.59083L1.64043 8.1675C1.7196 8.2325 1.7821 8.35833 1.76877 8.52583C1.74502 8.84178 1.74502 9.15906 1.76877 9.475C1.78127 9.64167 1.7196 9.76833 1.64127 9.83333L0.940434 10.41C0.65491 10.6451 0.462034 10.9738 0.396115 11.3378C0.330196 11.7017 0.395508 12.0772 0.580434 12.3975L1.34877 13.7283C1.53376 14.0487 1.8264 14.293 2.17462 14.4178C2.52285 14.5426 2.90406 14.5399 3.25043 14.41L4.0996 14.0917C4.19543 14.0558 4.33543 14.0658 4.4746 14.16C4.7346 14.3383 5.00877 14.4975 5.29543 14.635C5.4471 14.7083 5.52627 14.825 5.54293 14.9267L5.69127 15.8192C5.8171 16.5725 6.46877 17.125 7.23293 17.125H8.7696C9.53293 17.125 10.1854 16.5725 10.3113 15.8192L10.4596 14.9258C10.4763 14.8258 10.5546 14.7092 10.7071 14.635C10.9938 14.4975 11.2679 14.3383 11.5279 14.16C11.6671 14.065 11.8071 14.0558 11.9029 14.0917L12.7529 14.41C13.0992 14.5394 13.4801 14.5418 13.828 14.4168C14.1758 14.2919 14.4681 14.0476 14.6529 13.7275L15.4221 12.3967C15.607 12.0764 15.6723 11.7009 15.6064 11.3369C15.5405 10.973 15.3476 10.6443 15.0621 10.4092L14.3621 9.8325C14.2829 9.7675 14.2204 9.64167 14.2338 9.47417C14.2575 9.15822 14.2575 8.84095 14.2338 8.525C14.2204 8.35833 14.2829 8.23167 14.3613 8.16667L15.0613 7.59C15.6513 7.105 15.8038 6.265 15.4221 5.6025L14.6538 4.27167C14.4688 3.95132 14.1761 3.707 13.8279 3.58218C13.4797 3.45735 13.0985 3.46013 12.7521 3.59L11.9021 3.90833C11.8071 3.94417 11.6671 3.93417 11.5279 3.83917C11.2668 3.66025 10.9924 3.50145 10.7071 3.36417C10.5546 3.29167 10.4763 3.175 10.4596 3.07417L10.3104 2.18083C10.2497 1.81589 10.0614 1.48435 9.77905 1.24522C9.49674 1.0061 9.13874 0.874907 8.76877 0.875H7.23293H7.2321ZM8.00043 12.125C8.82923 12.125 9.62409 11.7958 10.2101 11.2097C10.7962 10.6237 11.1254 9.8288 11.1254 9C11.1254 8.1712 10.7962 7.37634 10.2101 6.79029C9.62409 6.20424 8.82923 5.875 8.00043 5.875C7.17163 5.875 6.37678 6.20424 5.79072 6.79029C5.20467 7.37634 4.87543 8.1712 4.87543 9C4.87543 9.8288 5.20467 10.6237 5.79072 11.2097C6.37678 11.7958 7.17163 12.125 8.00043 12.125Z"
+ fill="#989EA4"
+ />
+ </svg> \ No newline at end of file
diff --git a/packages/ui/shadcn/accordion.tsx b/packages/ui/shadcn/accordion.tsx
index a5dedb19..b24ecde4 100644
--- a/packages/ui/shadcn/accordion.tsx
+++ b/packages/ui/shadcn/accordion.tsx
@@ -2,7 +2,7 @@
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
-import { ChevronDown } from "lucide-react";
+import { ChevronDownIcon } from "@heroicons/react/24/outline";
import { cn } from "@repo/ui/lib/utils";
@@ -30,7 +30,8 @@ const AccordionTrigger = React.forwardRef<
{...props}
>
{children}
- <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
+
+ <ChevronDownIcon className="size-4 stroke-2 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
));
diff --git a/packages/ui/shadcn/switch.tsx b/packages/ui/shadcn/switch.tsx
new file mode 100644
index 00000000..3c246594
--- /dev/null
+++ b/packages/ui/shadcn/switch.tsx
@@ -0,0 +1,29 @@
+"use client";
+
+import * as React from "react";
+import * as SwitchPrimitives from "@radix-ui/react-switch";
+
+import { cn } from "@repo/ui/lib/utils";
+
+const Switch = React.forwardRef<
+ React.ElementRef<typeof SwitchPrimitives.Root>,
+ React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
+>(({ className, ...props }, ref) => (
+ <SwitchPrimitives.Root
+ className={cn(
+ "peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
+ className,
+ )}
+ {...props}
+ ref={ref}
+ >
+ <SwitchPrimitives.Thumb
+ className={cn(
+ "pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0",
+ )}
+ />
+ </SwitchPrimitives.Root>
+));
+Switch.displayName = SwitchPrimitives.Root.displayName;
+
+export { Switch };