aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDhravya Shah <[email protected]>2024-06-22 20:38:00 -0500
committerGitHub <[email protected]>2024-06-22 20:38:00 -0500
commit47e7528f675c187b0a771bc4040e8d8108c5ef8e (patch)
tree0a738b7a816f7b61865558be567c88d86db238c3
parentdelete packagelock (diff)
parentcleanup (diff)
downloadsupermemory-47e7528f675c187b0a771bc4040e8d8108c5ef8e.tar.xz
supermemory-47e7528f675c187b0a771bc4040e8d8108c5ef8e.zip
Merge pull request #79 from Dhravya/chathistory
addeed chathistory functionality
-rw-r--r--apps/web/app/(canvas)/canvas.css2
-rw-r--r--apps/web/app/(canvas)/canvas.tsx25
-rw-r--r--apps/web/app/(canvas)/dropComponent.tsx40
-rw-r--r--apps/web/app/(canvas)/lib/createEmbeds.ts289
-rw-r--r--apps/web/app/(canvas)/lib/loadSnap.ts6
-rw-r--r--apps/web/app/(canvas)/textCard.tsx40
-rw-r--r--apps/web/app/(dash)/chat/[chatid]/page.tsx38
-rw-r--r--apps/web/app/(dash)/chat/actions.ts0
-rw-r--r--apps/web/app/(dash)/chat/chatWindow.tsx472
-rw-r--r--apps/web/app/(dash)/chat/page.tsx108
-rw-r--r--apps/web/app/(dash)/dynamicisland.tsx22
-rw-r--r--apps/web/app/(dash)/header.tsx11
-rw-r--r--apps/web/app/(dash)/home/page.tsx18
-rw-r--r--apps/web/app/(dash)/home/queryinput.tsx4
-rw-r--r--apps/web/app/(dash)/layout.tsx5
-rw-r--r--apps/web/app/(dash)/menu.tsx7
-rw-r--r--apps/web/app/actions/doers.ts80
-rw-r--r--apps/web/app/actions/fetchers.ts102
-rw-r--r--apps/web/app/api/chat/route.ts9
-rw-r--r--apps/web/drizzle.config.ts2
-rw-r--r--apps/web/lib/searchParams.ts20
-rw-r--r--apps/web/migrations/000_setup.sql21
-rw-r--r--apps/web/migrations/meta/0000_snapshot.json117
-rw-r--r--apps/web/migrations/meta/_journal.json4
-rw-r--r--apps/web/server/db/schema.ts37
-rw-r--r--package.json2
-rw-r--r--packages/shared-types/index.ts25
-rw-r--r--packages/tailwind-config/globals.css13
-rw-r--r--packages/ui/components/QueryInput.tsx86
-rw-r--r--packages/ui/components/canvas/draggableComponent.tsx3
-rw-r--r--packages/ui/icons/index.ts4
-rw-r--r--packages/ui/shadcn/accordion.tsx5
-rw-r--r--packages/ui/shadcn/switch.tsx16
33 files changed, 1007 insertions, 626 deletions
diff --git a/apps/web/app/(canvas)/canvas.css b/apps/web/app/(canvas)/canvas.css
index f674e48f..3e6700da 100644
--- a/apps/web/app/(canvas)/canvas.css
+++ b/apps/web/app/(canvas)/canvas.css
@@ -1,3 +1,3 @@
.tlui-dialog__overlay {
- position: fixed;
+ position: fixed;
}
diff --git a/apps/web/app/(canvas)/canvas.tsx b/apps/web/app/(canvas)/canvas.tsx
index 72a23c92..aaf89a06 100644
--- a/apps/web/app/(canvas)/canvas.tsx
+++ b/apps/web/app/(canvas)/canvas.tsx
@@ -12,43 +12,40 @@ import { getAssetUrls } from "@tldraw/assets/selfHosted";
import { memo } from "react";
import DragContext from "./lib/context";
import DropZone from "./dropComponent";
-import './canvas.css'
+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")
+ console.log("entere");
};
-
+
useEffect(() => {
const divElement = Dragref.current;
if (divElement) {
- divElement.addEventListener('dragover', handleDragOver);
+ divElement.addEventListener("dragover", handleDragOver);
}
return () => {
if (divElement) {
- divElement.removeEventListener('dragover', handleDragOver);
+ 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",
});
@@ -94,4 +91,4 @@ const TldrawComponent =memo(() => {
</Tldraw>
</div>
);
-})
+});
diff --git a/apps/web/app/(canvas)/dropComponent.tsx b/apps/web/app/(canvas)/dropComponent.tsx
index a7898b5a..a14bd1f3 100644
--- a/apps/web/app/(canvas)/dropComponent.tsx
+++ b/apps/web/app/(canvas)/dropComponent.tsx
@@ -52,14 +52,14 @@ function DropZone() {
console.log("leaver");
};
- useEffect(()=> {
- setInterval(()=> {
+ useEffect(() => {
+ setInterval(() => {
editor.selectAll();
const shapes = editor.getSelectedShapes();
- const text = shapes.filter((s) => s.type === "text")
- console.log("hrhh", text)
- },5000)
- }, [])
+ const text = shapes.filter((s) => s.type === "text");
+ console.log("hrhh", text);
+ }, 5000);
+ }, []);
const handleDrop = useCallback((event: DragEvent) => {
event.preventDefault();
@@ -111,19 +111,23 @@ function DropZone() {
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}
>
- {
- 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>
+ {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>
);
}
diff --git a/apps/web/app/(canvas)/lib/createEmbeds.ts b/apps/web/app/(canvas)/lib/createEmbeds.ts
index 0ac3c7a5..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) {
@@ -115,17 +132,23 @@ function formatTextToRatio(text: string) {
if (currentLine) {
lines.push(currentLine);
}
- return {height: (lines.length+1)*18, width: maxLineWidth*10};
+ return { height: (lines.length + 1) * 18, width: maxLineWidth * 10 };
}
-export function handleExternalDroppedContent({text, editor}: {text:string, editor: Editor}){
+export function handleExternalDroppedContent({
+ text,
+ editor,
+}: {
+ text: string;
+ editor: Editor;
+}) {
const position = editor.inputs.shiftKey
- ? editor.inputs.currentPagePoint
- : editor.getViewportPageBounds().center;
+ ? editor.inputs.currentPagePoint
+ : editor.getViewportPageBounds().center;
- if (isURL(text)){
- createEmbedsFromUrl({editor, url: text})
- } else{
+ if (isURL(text)) {
+ createEmbedsFromUrl({ editor, url: text });
+ } else {
// editor.createShape({
// type: "text",
// x: position.x - 75,
@@ -136,66 +159,76 @@ export function handleExternalDroppedContent({text, editor}: {text:string, edito
// textAlign: "start",
// },
// });
- const {height, width} =formatTextToRatio(text)
+ const { height, width } = formatTextToRatio(text);
editor.createShape({
type: "Textcard",
- x: position.x - (width/2),
- y: position.y - (height/2),
- props: { content:text, extrainfo: "https://chatgpt.com/c/762cd44e-1752-495b-967a-aa3c23c6024a", w: width, h:height },
+ x: position.x - width / 2,
+ y: position.y - height / 2,
+ props: {
+ 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 a3d58b72..c6b748a9 100644
--- a/apps/web/app/(canvas)/lib/loadSnap.ts
+++ b/apps/web/app/(canvas)/lib/loadSnap.ts
@@ -1,6 +1,6 @@
import { createTLStore, defaultShapeUtils, loadSnapshot } from "tldraw";
import { twitterCardUtil } from "../twitterCard";
-import {textCardUtil} from "../textCard"
+import { textCardUtil } from "../textCard";
export async function loadRemoteSnapshot() {
const res = await fetch(
"https://learning-cf.pruthvirajthinks.workers.dev/get/page3",
@@ -9,6 +9,6 @@ export async function loadRemoteSnapshot() {
const newStore = createTLStore({
shapeUtils: [...defaultShapeUtils, twitterCardUtil, textCardUtil],
});
- loadSnapshot(newStore, 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
index 4517c521..065c5ae1 100644
--- a/apps/web/app/(canvas)/textCard.tsx
+++ b/apps/web/app/(canvas)/textCard.tsx
@@ -1,4 +1,11 @@
-import { BaseBoxShapeUtil, HTMLContainer, TLBaseBoxShape, TLBaseShape, useIsEditing, useValue } from "tldraw";
+import {
+ BaseBoxShapeUtil,
+ HTMLContainer,
+ TLBaseBoxShape,
+ TLBaseShape,
+ useIsEditing,
+ useValue,
+} from "tldraw";
type ITextCardShape = TLBaseShape<
"Textcard",
@@ -18,26 +25,29 @@ export class textCardUtil extends BaseBoxShapeUtil<ITextCardShape> {
}
component(s: ITextCardShape) {
-
- const isEditing = useIsEditing(s.id)
+ const isEditing = useIsEditing(s.id);
const isHoveringWhileEditingSameShape = useValue(
- 'is hovering',
+ "is hovering",
() => {
- const { editingShapeId, hoveredShapeId } = this.editor.getCurrentPageState()
-
+ 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
+ const editingShape = this.editor.getShape(editingShapeId);
+ if (
+ editingShape &&
+ this.editor.isShapeOfType<TLBaseBoxShape>(editingShape, "embed")
+ ) {
+ return true;
}
}
-
- return false
+
+ return false;
},
- []
- )
+ [],
+ );
- const isInteractive = isEditing || isHoveringWhileEditingSameShape
+ const isInteractive = isEditing || isHoveringWhileEditingSameShape;
return (
<HTMLContainer className="flex h-full w-full items-center justify-center">
<div
@@ -48,7 +58,7 @@ export class textCardUtil extends BaseBoxShapeUtil<ITextCardShape> {
zIndex: isInteractive ? "" : "-1",
background: "#2C3439",
borderRadius: "16px",
- padding: "8px 14px"
+ padding: "8px 14px",
}}
>
<h1 style={{ fontSize: "15px" }}>{s.props.content}</h1>
diff --git a/apps/web/app/(dash)/chat/[chatid]/page.tsx b/apps/web/app/(dash)/chat/[chatid]/page.tsx
new file mode 100644
index 00000000..e37ae07e
--- /dev/null
+++ b/apps/web/app/(dash)/chat/[chatid]/page.tsx
@@ -0,0 +1,38 @@
+import { getFullChatThread } from "@/app/actions/fetchers";
+import { chatSearchParamsCache } from "@/lib/searchParams";
+import ChatWindow from "../chatWindow";
+
+async function Page({
+ params,
+ searchParams,
+}: {
+ params: { chatid: string };
+ searchParams: Record<string, string | string[] | undefined>;
+}) {
+ const { firstTime, q, spaces } = chatSearchParamsCache.parse(searchParams);
+
+ let chat: Awaited<ReturnType<typeof getFullChatThread>>;
+
+ try {
+ chat = await getFullChatThread(params.chatid);
+ } catch (e) {
+ const error = e as Error;
+ return <div>This page errored out: {error.message}</div>;
+ }
+
+ if (!chat.success || !chat.data) {
+ console.error(chat.error);
+ return <div>Chat not found. Check the console for more details.</div>;
+ }
+
+ return (
+ <ChatWindow
+ q={q}
+ spaces={spaces}
+ initialChat={chat.data.length > 0 ? chat.data : undefined}
+ threadId={params.chatid}
+ />
+ );
+}
+
+export default Page;
diff --git a/apps/web/app/(dash)/chat/actions.ts b/apps/web/app/(dash)/chat/actions.ts
deleted file mode 100644
index e69de29b..00000000
--- a/apps/web/app/(dash)/chat/actions.ts
+++ /dev/null
diff --git a/apps/web/app/(dash)/chat/chatWindow.tsx b/apps/web/app/(dash)/chat/chatWindow.tsx
index 32fd1fce..8485d0b2 100644
--- a/apps/web/app/(dash)/chat/chatWindow.tsx
+++ b/apps/web/app/(dash)/chat/chatWindow.tsx
@@ -23,16 +23,18 @@ import { codeLanguageSubset } from "@/lib/constants";
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,
spaces,
-}: {
- q: string;
- spaces: { id: string; name: string }[];
-}) {
- const [layout, setLayout] = useState<"chat" | "initial">("initial");
- const [chatHistory, setChatHistory] = useState<ChatHistory[]>([
+ initialChat = [
{
question: q,
answer: {
@@ -40,8 +42,18 @@ function ChatWindow({
sources: [],
},
},
- ]);
- const [isAutoScroll, setIsAutoScroll] = useState(true);
+ ],
+ threadId,
+}: {
+ q: string;
+ spaces: { id: string; name: string }[];
+ initialChat?: ChatHistory[];
+ threadId: string;
+}) {
+ const [layout, setLayout] = useState<"chat" | "initial">(
+ initialChat.length > 1 ? "chat" : "initial",
+ );
+ const [chatHistory, setChatHistory] = useState<ChatHistory[]>(initialChat);
const removeJustificationFromText = (text: string) => {
// remove everything after the first "<justification>" word
@@ -61,7 +73,7 @@ function ChatWindow({
const getAnswer = async (query: string, spaces: string[]) => {
const sourcesFetch = await fetch(
- `/api/chat?q=${query}&spaces=${spaces}&sourcesOnly=true`,
+ `/api/chat?q=${query}&spaces=${spaces}&sourcesOnly=true&threadId=${threadId}`,
{
method: "POST",
body: JSON.stringify({ chatHistory }),
@@ -79,79 +91,108 @@ function ChatWindow({
const sourcesParsed = sourcesZod.safeParse(sources);
if (!sourcesParsed.success) {
- console.log(sources);
console.error(sourcesParsed.error);
toast.error("Something went wrong while getting the sources");
return;
}
+ window.scrollTo({
+ top: document.documentElement.scrollHeight,
+ behavior: "smooth",
+ });
- setChatHistory((prevChatHistory) => {
- window.scrollTo({
- top: document.documentElement.scrollHeight,
- behavior: "smooth",
- });
- const newChatHistory = [...prevChatHistory];
- const lastAnswer = newChatHistory[newChatHistory.length - 1];
- if (!lastAnswer) return prevChatHistory;
- const filteredSourceUrls = new Set(
- sourcesParsed.data.metadata.map((source) => source.url),
- );
- const uniqueSources = sourcesParsed.data.metadata.filter((source) => {
- if (filteredSourceUrls.has(source.url)) {
- filteredSourceUrls.delete(source.url);
- return true;
- }
- return false;
+ // Assuming this is part of a larger function within a React component
+ const updateChatHistoryAndFetch = async () => {
+ // Step 1: Update chat history with the assistant's response
+ await new Promise((resolve) => {
+ setChatHistory((prevChatHistory) => {
+ const newChatHistory = [...prevChatHistory];
+ const lastAnswer = newChatHistory[newChatHistory.length - 1];
+ if (!lastAnswer) {
+ resolve(undefined);
+ return prevChatHistory;
+ }
+
+ const filteredSourceUrls = new Set(
+ sourcesParsed.data.metadata.map((source) => source.url),
+ );
+ const uniqueSources = sourcesParsed.data.metadata.filter((source) => {
+ if (filteredSourceUrls.has(source.url)) {
+ filteredSourceUrls.delete(source.url);
+ return true;
+ }
+ return false;
+ });
+
+ lastAnswer.answer.sources = uniqueSources.map((source) => ({
+ title: source.title ?? "Untitled",
+ type: source.type ?? "page",
+ source: source.url ?? "https://supermemory.ai",
+ content: source.description ?? "No content available",
+ numChunks: sourcesParsed.data.metadata.filter(
+ (f) => f.url === source.url,
+ ).length,
+ }));
+
+ resolve(newChatHistory);
+ return newChatHistory;
+ });
});
- lastAnswer.answer.sources = uniqueSources.map((source) => ({
- title: source.title ?? "Untitled",
- type: source.type ?? "page",
- source: source.url ?? "https://supermemory.ai",
- content: source.description ?? "No content available",
- numChunks: sourcesParsed.data.metadata.filter(
- (f) => f.url === source.url,
- ).length,
- }));
- return newChatHistory;
- });
- const resp = await fetch(`/api/chat?q=${query}&spaces=${spaces}`, {
- method: "POST",
- body: JSON.stringify({ chatHistory }),
- });
+ // Step 2: Fetch data from the API
+ const resp = await fetch(
+ `/api/chat?q=${query}&spaces=${spaces}&threadId=${threadId}`,
+ {
+ method: "POST",
+ body: JSON.stringify({ chatHistory }),
+ },
+ );
- const reader = resp.body?.getReader();
- let done = false;
- while (!done && reader) {
- const { value, done: d } = await reader.read();
- done = d;
+ // Step 3: Read the response stream and update the chat history
+ const reader = resp.body?.getReader();
+ let done = false;
+ while (!done && reader) {
+ const { value, done: d } = await reader.read();
+ if (d) {
+ setChatHistory((prevChatHistory) => {
+ createChatObject(threadId, prevChatHistory);
+ return prevChatHistory;
+ });
+ }
+ done = d;
- setChatHistory((prevChatHistory) => {
- const newChatHistory = [...prevChatHistory];
- const lastAnswer = newChatHistory[newChatHistory.length - 1];
- if (!lastAnswer) return prevChatHistory;
const txt = new TextDecoder().decode(value);
+ setChatHistory((prevChatHistory) => {
+ const newChatHistory = [...prevChatHistory];
+ const lastAnswer = newChatHistory[newChatHistory.length - 1];
+ if (!lastAnswer) return prevChatHistory;
- if (isAutoScroll) {
window.scrollTo({
top: document.documentElement.scrollHeight,
behavior: "smooth",
});
- }
- lastAnswer.answer.parts.push({ text: txt });
- return newChatHistory;
- });
- }
+ lastAnswer.answer.parts.push({ text: txt });
+ return newChatHistory;
+ });
+ }
+ };
+
+ updateChatHistoryAndFetch();
};
useEffect(() => {
- if (q.trim().length > 0) {
+ if (q.trim().length > 0 || chatHistory.length > 0) {
setLayout("chat");
- getAnswer(
- q,
- spaces.map((s) => s.id),
- );
+ const lastChat = chatHistory.length > 0 ? chatHistory.length - 1 : 0;
+ const startGenerating = chatHistory[lastChat]?.answer.parts[0]?.text
+ ? false
+ : true;
+ if (startGenerating) {
+ getAnswer(
+ q,
+ spaces.map((s) => `${s}`),
+ );
+ }
} else {
router.push("/home");
}
@@ -177,150 +218,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
+ <h2
+ className={cn(
+ "text-white transition-all transform translate-y-0 opacity-100 duration-500 ease-in-out font-semibold text-xl",
+ )}
>
- <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>
+ {chat.question}
+ </h2>
- {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>
- )}
- <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>
- {/* 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
deleted file mode 100644
index 12b1bd2a..00000000
--- a/apps/web/app/(dash)/chat/page.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-import ChatWindow from "./chatWindow";
-import { chatSearchParamsCache } from "../../helpers/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");
-
-function Page({
- searchParams,
-}: {
- searchParams: Record<string, string | string[] | undefined>;
-}) {
- const { firstTime, q, spaces } = chatSearchParamsCache.parse(searchParams);
-
- console.log(spaces);
-
- 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 98fafc7a..43db52f3 100644
--- a/apps/web/app/(dash)/dynamicisland.tsx
+++ b/apps/web/app/(dash)/dynamicisland.tsx
@@ -71,16 +71,13 @@ function DynamicIslandContent() {
}
const lastBtn = useRef<string>();
- useEffect(() => {
- console.log(show);
- }, [show]);
useEffect(() => {
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
setshow(true);
}
- console.log(e.key, lastBtn.current);
+
if (e.key === "a" && lastBtn.current === "Alt") {
setshow(false);
}
@@ -90,19 +87,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} />
)}
</>
);
@@ -272,7 +265,6 @@ function PageForm({
spaces: space ? [space] : undefined,
});
- console.log(cont);
setLoading(false);
if (cont.success) {
toast.success("Memory created");
diff --git a/apps/web/app/(dash)/header.tsx b/apps/web/app/(dash)/header.tsx
index 040097fa..91c00125 100644
--- a/apps/web/app/(dash)/header.tsx
+++ b/apps/web/app/(dash)/header.tsx
@@ -9,7 +9,6 @@ import DynamicIsland from "./dynamicisland";
function Header() {
return (
<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
@@ -20,14 +19,7 @@ function Header() {
</Link>
<div className="fixed z-30 left-1/2 -translate-x-1/2 top-5">
- {/* <DynamicIsland /> */}
- <button className="bg-secondary p-2 text-[#989EA4] rounded-full flex items-center justify-between gap-2 px-4 h-10 pr-5">
- <Image
- src={AddIcon}
- alt="add icon"
- />
- Add content
- </button>
+ <DynamicIsland />
</div>
<button className="flex duration-200 items-center text-[#7D8994] hover:bg-[#1F2429] text-[13px] gap-2 px-3 py-2 rounded-xl">
@@ -35,7 +27,6 @@ function Header() {
Start new chat
</button>
</div>
-
</div>
);
}
diff --git a/apps/web/app/(dash)/home/page.tsx b/apps/web/app/(dash)/home/page.tsx
index bdf6a61e..b6cfd223 100644
--- a/apps/web/app/(dash)/home/page.tsx
+++ b/apps/web/app/(dash)/home/page.tsx
@@ -5,6 +5,7 @@ import QueryInput from "./queryinput";
import { homeSearchParamsCache } from "@/lib/searchParams";
import { getSpaces } from "@/app/actions/fetchers";
import { useRouter } from "next/navigation";
+import { createChatThread } from "@/app/actions/doers";
function Page({
searchParams,
@@ -12,7 +13,8 @@ function Page({
searchParams: Record<string, string | string[] | undefined>;
}) {
// TODO: use this to show a welcome page/modal
- const { firstTime } = homeSearchParamsCache.parse(searchParams);
+ // const { firstTime } = homeSearchParamsCache.parse(searchParams);
+ const { push } = useRouter();
const [spaces, setSpaces] = useState<{ id: number; name: string }[]>([]);
@@ -20,13 +22,12 @@ function Page({
getSpaces().then((res) => {
if (res.success && res.data) {
setSpaces(res.data);
+ return;
}
// TODO: HANDLE ERROR
});
}, []);
- const { push } = useRouter();
-
return (
<div className="max-w-3xl h-full justify-center flex mx-auto w-full flex-col">
{/* all content goes here */}
@@ -34,13 +35,12 @@ function Page({
<div className="w-full pb-20">
<QueryInput
- handleSubmit={(q, spaces) => {
- const newQ =
- "/chat?q=" +
- encodeURI(q) +
- (spaces ? "&spaces=" + JSON.stringify(spaces) : "");
+ handleSubmit={async (q, spaces) => {
+ const threadid = await createChatThread(q);
- push(newQ);
+ push(
+ `/chat/${threadid.data}?spaces=${JSON.stringify(spaces)}&q=${q}`,
+ );
}}
initialSpaces={spaces}
/>
diff --git a/apps/web/app/(dash)/home/queryinput.tsx b/apps/web/app/(dash)/home/queryinput.tsx
index 4fadfb6f..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 placeholder:text-[#5D6165] text-[#9DA0A4] focus:text-white 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,7 +85,7 @@ function QueryInput({
<button
type="submit"
- onClick={e => e.preventDefault()}
+ 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 4e1f6989..1666aa1c 100644
--- a/apps/web/app/(dash)/layout.tsx
+++ b/apps/web/app/(dash)/layout.tsx
@@ -13,16 +13,13 @@ async function Layout({ children }: { children: React.ReactNode }) {
return (
<main className="h-screen flex flex-col">
-
<div className="fixed top-0 left-0 w-full">
<Header />
</div>
<Menu />
- <div className="w-full h-full">
- {children}
- </div>
+ <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 f40c22e4..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, CanvasIcon } from "@repo/ui/icons";
+import {
+ MemoriesIcon,
+ ExploreIcon,
+ HistoryIcon,
+ CanvasIcon,
+} from "@repo/ui/icons";
import Link from "next/link";
function Menu() {
diff --git a/apps/web/app/actions/doers.ts b/apps/web/app/actions/doers.ts
index 6c7180d9..8833d5d2 100644
--- a/apps/web/app/actions/doers.ts
+++ b/apps/web/app/actions/doers.ts
@@ -2,7 +2,13 @@
import { revalidatePath } from "next/cache";
import { db } from "../../server/db";
-import { contentToSpace, space, storedContent } from "../../server/db/schema";
+import {
+ chatHistory,
+ chatThreads,
+ contentToSpace,
+ space,
+ storedContent,
+} from "../../server/db/schema";
import { ServerActionReturnType } from "./types";
import { auth } from "../../server/auth";
import { Tweet } from "react-tweet/api";
@@ -10,6 +16,7 @@ import { getMetaData } from "@/lib/get-metadata";
import { and, eq, inArray, sql } from "drizzle-orm";
import { LIMITS } from "@/lib/constants";
import { z } from "zod";
+import { ChatHistory } from "@repo/shared-types";
export const createSpace = async (
input: string | FormData,
@@ -266,3 +273,74 @@ export const createMemory = async (input: {
};
}
};
+
+export const createChatThread = async (
+ firstMessage: string,
+): ServerActionReturnType<string> => {
+ const data = await auth();
+
+ if (!data || !data.user || !data.user.id) {
+ return { error: "Not authenticated", success: false };
+ }
+
+ const thread = await db
+ .insert(chatThreads)
+ .values({
+ firstMessage,
+ userId: data.user.id,
+ })
+ .returning({ id: chatThreads.id })
+ .execute();
+
+ console.log(thread);
+
+ if (!thread[0]) {
+ return {
+ success: false,
+ error: "Failed to create chat thread",
+ };
+ }
+
+ return { success: true, data: thread[0].id };
+};
+
+export const createChatObject = async (
+ threadId: string,
+ chatHistorySoFar: ChatHistory[],
+): ServerActionReturnType<boolean> => {
+ const data = await auth();
+
+ if (!data || !data.user || !data.user.id) {
+ return { error: "Not authenticated", success: false };
+ }
+
+ const lastChat = chatHistorySoFar[chatHistorySoFar.length - 1];
+ if (!lastChat) {
+ return {
+ success: false,
+ data: false,
+ error: "No chat object found",
+ };
+ }
+ console.log("sources: ", lastChat.answer.sources);
+
+ const saved = await db.insert(chatHistory).values({
+ question: lastChat.question,
+ answer: lastChat.answer.parts.map((part) => part.text).join(""),
+ answerSources: JSON.stringify(lastChat.answer.sources),
+ threadId,
+ });
+
+ if (!saved) {
+ return {
+ success: false,
+ data: false,
+ error: "Failed to save chat object",
+ };
+ }
+
+ return {
+ success: true,
+ data: true,
+ };
+};
diff --git a/apps/web/app/actions/fetchers.ts b/apps/web/app/actions/fetchers.ts
index dc71252e..708b82b2 100644
--- a/apps/web/app/actions/fetchers.ts
+++ b/apps/web/app/actions/fetchers.ts
@@ -1,8 +1,10 @@
"use server";
-import { eq, inArray, not, sql } from "drizzle-orm";
+import { and, asc, eq, inArray, not, sql } from "drizzle-orm";
import { db } from "../../server/db";
import {
+ chatHistory,
+ chatThreads,
Content,
contentToSpace,
storedContent,
@@ -10,6 +12,8 @@ import {
} from "../../server/db/schema";
import { ServerActionReturnType, Space } from "./types";
import { auth } from "../../server/auth";
+import { ChatHistory, SourceZod } from "@repo/shared-types";
+import { z } from "zod";
export const getSpaces = async (): ServerActionReturnType<Space[]> => {
const data = await auth();
@@ -103,22 +107,27 @@ export const getAllUserMemoriesAndSpaces = async (): ServerActionReturnType<{
// console.log(contentCountBySpace);
// get a count with space mappings like spaceID: count (number of memories in that space)
- const contentCountBySpace = await db
- .select({
- spaceId: contentToSpace.spaceId,
- count: sql<number>`count(*)`.mapWith(Number),
- })
- .from(contentToSpace)
- .where(
- inArray(
- contentToSpace.spaceId,
- spacesWithoutUser.map((space) => space.id),
- ),
- )
- .groupBy(contentToSpace.spaceId)
- .execute();
- console.log(contentCountBySpace);
+ const len = spacesWithoutUser.map((space) => space.id).length;
+
+ if (len > 0) {
+ const contentCountBySpace = await db
+ .select({
+ spaceId: contentToSpace.spaceId,
+ count: sql<number>`count(*)`.mapWith(Number),
+ })
+ .from(contentToSpace)
+ .where(
+ inArray(
+ contentToSpace.spaceId,
+ spacesWithoutUser.map((space) => space.id),
+ ),
+ )
+ .groupBy(contentToSpace.spaceId)
+ .execute();
+
+ console.log(contentCountBySpace);
+ }
const contentNotInAnySpace = await db
.select()
@@ -140,3 +149,64 @@ export const getAllUserMemoriesAndSpaces = async (): ServerActionReturnType<{
data: { spaces: spacesWithoutUser, memories: contentNotInAnySpace },
};
};
+
+export const getFullChatThread = async (
+ threadId: string,
+): ServerActionReturnType<ChatHistory[]> => {
+ const data = await auth();
+
+ if (!data || !data.user || !data.user.id) {
+ return { error: "Not authenticated", success: false };
+ }
+
+ const thread = await db.query.chatThreads.findFirst({
+ where: and(
+ eq(chatThreads.id, threadId),
+ eq(chatThreads.userId, data.user.id),
+ ),
+ });
+
+ if (!thread) {
+ return { error: "Thread not found", success: false };
+ }
+
+ const allChatsInThisThread = await db.query.chatHistory
+ .findMany({
+ where: and(eq(chatHistory.threadId, threadId)),
+ orderBy: asc(chatHistory.id),
+ })
+ .execute();
+
+ const accumulatedChatHistory: ChatHistory[] = allChatsInThisThread.map(
+ (chat) => {
+ console.log("answer sources", chat.answerSources);
+ const sourceCheck = z
+ .array(SourceZod)
+ .safeParse(JSON.parse(chat.answerSources ?? "[]"));
+
+ if (!sourceCheck.success || !sourceCheck.data) {
+ console.error("sourceCheck.error", sourceCheck.error);
+ throw new Error("Invalid source data");
+ }
+
+ const sources = sourceCheck.data;
+
+ return {
+ question: chat.question,
+ answer: {
+ parts: [
+ {
+ text: chat.answer ?? undefined,
+ },
+ ],
+ sources: sources ?? [],
+ },
+ };
+ },
+ );
+
+ return {
+ success: true,
+ data: accumulatedChatHistory,
+ };
+};
diff --git a/apps/web/app/api/chat/route.ts b/apps/web/app/api/chat/route.ts
index c19ce92b..d0e53066 100644
--- a/apps/web/app/api/chat/route.ts
+++ b/apps/web/app/api/chat/route.ts
@@ -1,9 +1,5 @@
import { type NextRequest } from "next/server";
-import {
- ChatHistory,
- ChatHistoryZod,
- convertChatHistoryList,
-} from "@repo/shared-types";
+import { ChatHistoryZod, convertChatHistoryList } from "@repo/shared-types";
import { ensureAuth } from "../ensureAuth";
import { z } from "zod";
@@ -67,11 +63,8 @@ export async function POST(req: NextRequest) {
},
);
- console.log("sourcesOnly", sourcesOnly);
-
if (sourcesOnly == "true") {
const data = await resp.json();
- console.log("data", data);
return new Response(JSON.stringify(data), { status: 200 });
}
diff --git a/apps/web/drizzle.config.ts b/apps/web/drizzle.config.ts
index ab071121..521e1fcb 100644
--- a/apps/web/drizzle.config.ts
+++ b/apps/web/drizzle.config.ts
@@ -1,7 +1,7 @@
import { type Config } from "drizzle-kit";
export default {
- schema: "./app/helpers/server/db/schema.ts",
+ schema: "./server/db/schema.ts",
dialect: "sqlite",
driver: "d1",
dbCredentials: {
diff --git a/apps/web/lib/searchParams.ts b/apps/web/lib/searchParams.ts
index 9899eaf7..6db718c2 100644
--- a/apps/web/lib/searchParams.ts
+++ b/apps/web/lib/searchParams.ts
@@ -16,11 +16,19 @@ export const chatSearchParamsCache = createSearchParamsCache({
firstTime: parseAsBoolean.withDefault(false),
q: parseAsString.withDefault(""),
spaces: parseAsArrayOf(
- parseAsJson(() =>
- z.object({
- id: z.string(),
- name: z.string(),
- }),
- ),
+ parseAsJson((c) => {
+ const valid = z
+ .object({
+ id: z.string(),
+ name: z.string(),
+ })
+ .safeParse(c);
+
+ if (!valid.success) {
+ return null;
+ }
+
+ return valid.data;
+ }),
).withDefault([]),
});
diff --git a/apps/web/migrations/000_setup.sql b/apps/web/migrations/000_setup.sql
index 0c151b98..7e4275b8 100644
--- a/apps/web/migrations/000_setup.sql
+++ b/apps/web/migrations/000_setup.sql
@@ -27,6 +27,23 @@ CREATE TABLE `authenticator` (
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
+CREATE TABLE `chatHistory` (
+ `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
+ `threadId` text NOT NULL,
+ `question` text NOT NULL,
+ `answerParts` text,
+ `answerSources` text,
+ `answerJustification` text,
+ FOREIGN KEY (`threadId`) REFERENCES `chatThread`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
+CREATE TABLE `chatThread` (
+ `id` text PRIMARY KEY NOT NULL,
+ `firstMessage` text NOT NULL,
+ `userId` text NOT NULL,
+ FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
+);
+--> statement-breakpoint
CREATE TABLE `contentToSpace` (
`contentId` integer NOT NULL,
`spaceId` integer NOT NULL,
@@ -60,7 +77,7 @@ CREATE TABLE `storedContent` (
`ogImage` text(255),
`type` text DEFAULT 'page',
`image` text(255),
- `user` integer,
+ `user` text,
FOREIGN KEY (`user`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
@@ -80,6 +97,8 @@ CREATE TABLE `verificationToken` (
);
--> statement-breakpoint
CREATE UNIQUE INDEX `authenticator_credentialID_unique` ON `authenticator` (`credentialID`);--> statement-breakpoint
+CREATE INDEX `chatHistory_thread_idx` ON `chatHistory` (`threadId`);--> statement-breakpoint
+CREATE INDEX `chatThread_user_idx` ON `chatThread` (`userId`);--> statement-breakpoint
CREATE UNIQUE INDEX `space_name_unique` ON `space` (`name`);--> statement-breakpoint
CREATE INDEX `spaces_name_idx` ON `space` (`name`);--> statement-breakpoint
CREATE INDEX `spaces_user_idx` ON `space` (`user`);--> statement-breakpoint
diff --git a/apps/web/migrations/meta/0000_snapshot.json b/apps/web/migrations/meta/0000_snapshot.json
index 20327dda..3e197cbd 100644
--- a/apps/web/migrations/meta/0000_snapshot.json
+++ b/apps/web/migrations/meta/0000_snapshot.json
@@ -1,7 +1,7 @@
{
"version": "6",
"dialect": "sqlite",
- "id": "4a568d9b-a0e6-44ed-946b-694e34b063f3",
+ "id": "349eea0d-f26e-4579-9c65-3982816b0c6c",
"prevId": "00000000-0000-0000-0000-000000000000",
"tables": {
"account": {
@@ -191,6 +191,119 @@
},
"uniqueConstraints": {}
},
+ "chatHistory": {
+ "name": "chatHistory",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "threadId": {
+ "name": "threadId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "question": {
+ "name": "question",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "answerParts": {
+ "name": "answerParts",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "answerSources": {
+ "name": "answerSources",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "answerJustification": {
+ "name": "answerJustification",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "chatHistory_thread_idx": {
+ "name": "chatHistory_thread_idx",
+ "columns": ["threadId"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "chatHistory_threadId_chatThread_id_fk": {
+ "name": "chatHistory_threadId_chatThread_id_fk",
+ "tableFrom": "chatHistory",
+ "tableTo": "chatThread",
+ "columnsFrom": ["threadId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "chatThread": {
+ "name": "chatThread",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "firstMessage": {
+ "name": "firstMessage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userId": {
+ "name": "userId",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "chatThread_user_idx": {
+ "name": "chatThread_user_idx",
+ "columns": ["userId"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {
+ "chatThread_userId_user_id_fk": {
+ "name": "chatThread_userId_user_id_fk",
+ "tableFrom": "chatThread",
+ "tableTo": "user",
+ "columnsFrom": ["userId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
"contentToSpace": {
"name": "contentToSpace",
"columns": {
@@ -411,7 +524,7 @@
},
"user": {
"name": "user",
- "type": "integer",
+ "type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
diff --git a/apps/web/migrations/meta/_journal.json b/apps/web/migrations/meta/_journal.json
index 90bb9df7..0babac49 100644
--- a/apps/web/migrations/meta/_journal.json
+++ b/apps/web/migrations/meta/_journal.json
@@ -5,8 +5,8 @@
{
"idx": 0,
"version": "6",
- "when": 1718412145023,
- "tag": "0000_absurd_pandemic",
+ "when": 1719075265633,
+ "tag": "0000_conscious_arachne",
"breakpoints": true
}
]
diff --git a/apps/web/server/db/schema.ts b/apps/web/server/db/schema.ts
index 1ff23c82..f54d2094 100644
--- a/apps/web/server/db/schema.ts
+++ b/apps/web/server/db/schema.ts
@@ -154,3 +154,40 @@ export type StoredSpace = typeof space.$inferSelect;
export type ChachedSpaceContent = StoredContent & {
space: number;
};
+
+export const chatThreads = createTable(
+ "chatThread",
+ {
+ id: text("id")
+ .notNull()
+ .primaryKey()
+ .$defaultFn(() => crypto.randomUUID()),
+ firstMessage: text("firstMessage").notNull(),
+ userId: text("userId")
+ .notNull()
+ .references(() => users.id, { onDelete: "cascade" }),
+ },
+ (thread) => ({
+ userIdx: index("chatThread_user_idx").on(thread.userId),
+ }),
+);
+
+export const chatHistory = createTable(
+ "chatHistory",
+ {
+ id: integer("id").notNull().primaryKey({ autoIncrement: true }),
+ threadId: text("threadId")
+ .notNull()
+ .references(() => chatThreads.id, { onDelete: "cascade" }),
+ question: text("question").notNull(),
+ answer: text("answerParts"), // Single answer part as string
+ answerSources: text("answerSources"), // JSON stringified array of objects
+ answerJustification: text("answerJustification"),
+ },
+ (history) => ({
+ threadIdx: index("chatHistory_thread_idx").on(history.threadId),
+ }),
+);
+
+export type ChatThread = typeof chatThreads.$inferSelect;
+export type ChatHistory = typeof chatHistory.$inferSelect;
diff --git a/package.json b/package.json
index 56f44944..35b90102 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,7 @@
"@aws-sdk/s3-request-presigner": "^3.577.0",
"@cloudflare/puppeteer": "^0.0.11",
"@headlessui/react": "^2.0.4",
- "@heroicons/react": "^2.1.3",
+ "@heroicons/react": "^2.1.4",
"@hono/swagger-ui": "^0.2.2",
"@hookform/resolvers": "^3.4.2",
"@iarna/toml": "^2.2.5",
diff --git a/packages/shared-types/index.ts b/packages/shared-types/index.ts
index d3f466e1..318684b7 100644
--- a/packages/shared-types/index.ts
+++ b/packages/shared-types/index.ts
@@ -1,18 +1,20 @@
import { z } from "zod";
+export const SourceZod = z.object({
+ type: z.string(),
+ source: z.string(),
+ title: z.string(),
+ content: z.string(),
+ numChunks: z.number().optional().default(1),
+});
+
+export type Source = z.infer<typeof SourceZod>;
+
export const ChatHistoryZod = z.object({
question: z.string(),
answer: z.object({
- parts: z.array(z.object({ text: z.string() })),
- sources: z.array(
- z.object({
- type: z.enum(["note", "page", "tweet"]),
- source: z.string(),
- title: z.string(),
- content: z.string(),
- numChunks: z.number().optional().default(1),
- }),
- ),
+ parts: z.array(z.object({ text: z.string().optional() })),
+ sources: z.array(SourceZod),
justification: z.string().optional(),
}),
});
@@ -52,5 +54,8 @@ export function convertChatHistoryList(
);
});
+ // THE LAST ASSISTANT CONTENT WILL ALWAYS BE EMPTY, so we remove it
+ convertedChats.pop();
+
return convertedChats;
}
diff --git a/packages/tailwind-config/globals.css b/packages/tailwind-config/globals.css
index 93099e8d..d09b120b 100644
--- a/packages/tailwind-config/globals.css
+++ b/packages/tailwind-config/globals.css
@@ -211,12 +211,13 @@ body {
width: 0px;
}
-::-moz-selection { /* Code for Firefox */
- color: #369DFD;
- background: #21303D;
+::-moz-selection {
+ /* Code for Firefox */
+ color: #369dfd;
+ background: #21303d;
}
::selection {
- color: #369DFD;
- background: #21303D;
-} \ No newline at end of file
+ color: #369dfd;
+ background: #21303d;
+}
diff --git a/packages/ui/components/QueryInput.tsx b/packages/ui/components/QueryInput.tsx
index 7188d667..ba476dda 100644
--- a/packages/ui/components/QueryInput.tsx
+++ b/packages/ui/components/QueryInput.tsx
@@ -1,46 +1,46 @@
-import React from 'react'
-import Divider from '../shadcn/divider'
-import { ArrowRightIcon } from '../icons'
-import Image from 'next/image'
+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}
- />
+ 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>
+ <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
+ {/* <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}
@@ -52,9 +52,9 @@ function QueryInput() {
</p>
}
/> */}
- {/* </div> */}
- </div>
- )
+ {/* </div> */}
+ </div>
+ );
}
-export default QueryInput \ No newline at end of file
+export default QueryInput;
diff --git a/packages/ui/components/canvas/draggableComponent.tsx b/packages/ui/components/canvas/draggableComponent.tsx
index f9531f96..d0832e81 100644
--- a/packages/ui/components/canvas/draggableComponent.tsx
+++ b/packages/ui/components/canvas/draggableComponent.tsx
@@ -51,14 +51,13 @@ function DraggableComponents({
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]"}`}
+ 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">
diff --git a/packages/ui/icons/index.ts b/packages/ui/icons/index.ts
index 88d4ebc0..cf2f0aaa 100644
--- a/packages/ui/icons/index.ts
+++ b/packages/ui/icons/index.ts
@@ -8,7 +8,7 @@ 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 CanvasIcon from "./canvas.svg";
import blockIcon from "./block.svg";
import LinkIcon from "./link.svg";
import AutocompleteIcon from "./autocomplete.svg";
@@ -33,5 +33,5 @@ export {
AutocompleteIcon,
BlockIcon,
DragIcon,
- SettingsIcon
+ SettingsIcon,
};
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
index 78a67682..3c246594 100644
--- a/packages/ui/shadcn/switch.tsx
+++ b/packages/ui/shadcn/switch.tsx
@@ -1,7 +1,7 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as SwitchPrimitives from "@radix-ui/react-switch"
+import * as React from "react";
+import * as SwitchPrimitives from "@radix-ui/react-switch";
import { cn } from "@repo/ui/lib/utils";
@@ -12,18 +12,18 @@ const Switch = React.forwardRef<
<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
+ 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"
+ "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
+));
+Switch.displayName = SwitchPrimitives.Root.displayName;
-export { Switch }
+export { Switch };