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(false); - const Dragref = useRef(null) + const Dragref = useRef(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 ( -
- -
+
+ +
); }); -const TldrawComponent =memo(() => { +const TldrawComponent = memo(() => { const [storeWithStatus, setStoreWithStatus] = useState({ status: "loading", }); @@ -94,4 +91,4 @@ const TldrawComponent =memo(() => { ); -}) +}); 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&& ( - <> -
-
-
-
+ {isDraggingOver && ( + <> +
+ +
+
+ +
+
+ +
+
+ +

Drop here to add Content on Canvas

- - ) - - } - + + )} ); } 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, - }); - } - - 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.updateShapes([ - { - id: shape.id, - type: shape.type, - props: { - assetId: asset.id, - }, - }, - ]); + 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; + } + } + + editor.batch(() => { + if (shouldAlsoCreateAsset) { + editor.createAssets([asset]); + } + + 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() + // 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) + 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, - } - }) - ) - } + 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() - } + // 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, - }, - } + 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) - }) + editor.batch(() => { + editor.createShapes([partial]).select(partial.id); + centerSelectionAroundPoint(editor, position); + }); - return editor.getShape(partial.id) as TLBookmarkShape -} \ No newline at end of file + 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 { } 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(editingShape, 'embed')) { - return true + const editingShape = this.editor.getShape(editingShapeId); + if ( + editingShape && + this.editor.isShapeOfType(editingShape, "embed") + ) { + return true; } } - - return false - }, - [] - ) - const isInteractive = isEditing || isHoveringWhileEditingSameShape + return false; + }, + [], + ); + + const isInteractive = isEditing || isHoveringWhileEditingSameShape; return (
{ zIndex: isInteractive ? "" : "-1", background: "#2C3439", borderRadius: "16px", - padding: "8px 14px" + padding: "8px 14px", }} >

{s.props.content}

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; +}) { + const { firstTime, q, spaces } = chatSearchParamsCache.parse(searchParams); + + let chat: Awaited>; + + try { + chat = await getFullChatThread(params.chatid); + } catch (e) { + const error = e as Error; + return
This page errored out: {error.message}
; + } + + if (!chat.success || !chat.data) { + console.error(chat.error); + return
Chat not found. Check the console for more details.
; + } + + return ( + 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 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([ + 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(initialChat); const removeJustificationFromText = (text: string) => { // remove everything after the first "" 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", + // 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; + }); }); - const newChatHistory = [...prevChatHistory]; - const lastAnswer = newChatHistory[newChatHistory.length - 1]; - if (!lastAnswer) return prevChatHistory; - const filteredSourceUrls = new Set( - sourcesParsed.data.metadata.map((source) => source.url), + + // 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 uniqueSources = sourcesParsed.data.metadata.filter((source) => { - if (filteredSourceUrls.has(source.url)) { - filteredSourceUrls.delete(source.url); - return true; + + // 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; + }); } - 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, - })); - return newChatHistory; - }); + done = d; - const resp = await fetch(`/api/chat?q=${query}&spaces=${spaces}`, { - 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; - - 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({ ) : (
- {chatHistory.map((chat, idx) => ( -
-

- {chat.question} -

- -
+
+ {chatHistory.map((chat, idx) => ( +
0 || chat.answer.parts.length === 0 ? "flex" : "hidden"}`} + className={`mt-8 ${idx != chatHistory.length - 1 ? "pb-2 border-b border-b-gray-400" : ""}`} > - - - - Related Memories - - {/* TODO: fade out content on the right side, the fade goes away when the user scrolls */} - - {/* Loading state */} - {chat.answer.sources.length > 0 || - (chat.answer.parts.length === 0 && ( - <> - {[1, 2, 3, 4].map((_, idx) => ( -
-
-
-
- ))} - - ))} - {chat.answer.sources.map((source, idx) => ( - -
- {source.type} - - {source.numChunks > 1 && ( - {source.numChunks} chunks - )} -
-
{source.title}
-
- {source.content.length > 100 - ? source.content.slice(0, 100) + "..." - : source.content} -
- - ))} -
-
-
-
- - {/* Summary */} -
-
Summary
-
- {chat.answer.parts.length === 0 && ( -
-
-
-
-
-
+

- {removeJustificationFromText( - chat.answer.parts.map((part) => part.text).join(""), - )} - -

-
- {/* Justification */} - {chat.answer.justification && - chat.answer.justification.length && ( + > + {chat.question} + + +
+ {/* Related memories */}
0 ? "flex" : "hidden"}`} + className={`space-y-4 ${chat.answer.sources.length > 0 || chat.answer.parts.length === 0 ? "flex" : "hidden"}`} > - - + + - Justification + Related Memories + {/* TODO: fade out content on the right side, the fade goes away when the user scrolls */} - {chat.answer.justification.length > 0 - ? chat.answer.justification - .replaceAll("", "") - .replaceAll("", "") - : "No justification provided."} + {/* Loading state */} + {chat.answer.sources.length > 0 || + (chat.answer.parts.length === 0 && ( + <> + {[1, 2, 3, 4].map((_, idx) => ( +
+
+
+
+ ))} + + ))} + {chat.answer.sources.map((source, idx) => ( + +
+ {source.type} + + {source.numChunks > 1 && ( + {source.numChunks} chunks + )} +
+
+ {source.title} +
+
+ {source.content.length > 100 + ? source.content.slice(0, 100) + "..." + : source.content} +
+ + ))}
- )} -
-
- ))} -
+ {/* Summary */} +
+
Summary
+
+ {/* Loading state */} + {(chat.answer.parts.length === 0 || + chat.answer.parts.join("").length === 0) && ( +
+
+
+
+
+
+ )} + + + {removeJustificationFromText( + chat.answer.parts + .map((part) => part.text) + .join(""), + )} + + +
+ {/* TODO: speak response */} + {/* */} + {/* copy response */} + + +
+
+
+ {/* Justification */} + {chat.answer.justification && + chat.answer.justification.length && ( +
0 ? "flex" : "hidden"}`} + > + + + + Justification + + + {chat.answer.justification.length > 0 + ? chat.answer.justification + .replaceAll("", "") + .replaceAll("", "") + : "No justification provided."} + + + +
+ )} +
+
+
+ ))} +
+ +
; -}) { - const { firstTime, q, spaces } = chatSearchParamsCache.parse(searchParams); - - console.log(spaces); - - return ( -
- {/* */} - -
- {/* single q&A */} - {Array.from({ length: 1 }).map((_, i) => ( - -
- - {/* header */} -
- {/* query */} -

Why is Retrieval-Augmented Generation important?

-
- - {/* response */} -
- - {/* related memories */} -
- {/* section header */} -
-

Related memories

- -
- - {/* section content */} - {/* collection of memories */} -
- {/* related memory */} - {Array.from({ length: 3 }).map((_, i) => ( -
- -

Webpage

-

What is RAG? - Retrieval-Augmented Generation Explained - AWS

-
- ))} -
-
- - {/* summary */} -
- {/* section header */} -
-

Summary

- -
- - {/* section content */} -
-

- 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. -

- - {/* response actions */} -
- {/* speak response */} - - {/* copy response */} - -
-
- -
- -
- -
- ))} - -
- -
- -
- -
- ); -} - -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(); - 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 ? ( -
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" > -
- Add icon - Add Content -
-
+ add icon + Add content + ) : ( -
- -
+ )} ); @@ -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 (
-
- {/* */} - +
-
); } 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; }) { // 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 (
{/* all content goes here */} @@ -34,13 +35,12 @@ function Page({
{ - 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({