From 06062c361de50f8b7e0e783879ea30a441c9c104 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Thu, 6 Mar 2025 15:58:02 -0800 Subject: [PATCH 01/35] feat: Better sidebar --- src/components/thread/history/index.tsx | 62 +++++++++++++------ src/components/thread/index.tsx | 80 ++++++++++++++++++++----- src/hooks/useMediaQuery.tsx | 16 +++++ src/index.css | 2 + tailwind.config.js | 6 +- 5 files changed, 132 insertions(+), 34 deletions(-) create mode 100644 src/hooks/useMediaQuery.tsx diff --git a/src/components/thread/history/index.tsx b/src/components/thread/history/index.tsx index 79f123d..7d0fbec 100644 --- a/src/components/thread/history/index.tsx +++ b/src/components/thread/history/index.tsx @@ -2,6 +2,7 @@ import { Button } from "@/components/ui/button"; import { useThreads } from "@/providers/Thread"; import { Thread } from "@langchain/langgraph-sdk"; import { useEffect } from "react"; + import { getContentString } from "../utils"; import { useQueryParam, StringParam, BooleanParam } from "use-query-params"; import { @@ -11,6 +12,9 @@ import { SheetTitle, } from "@/components/ui/sheet"; import { Skeleton } from "@/components/ui/skeleton"; +import { cn } from "@/lib/utils"; +import { PanelRightOpen } from "lucide-react"; +import { useMediaQuery } from "@/hooks/useMediaQuery"; function ThreadList({ threads, @@ -22,7 +26,7 @@ function ThreadList({ const [threadId, setThreadId] = useQueryParam("threadId", StringParam); return ( -
+
{threads.map((t) => { let itemText = t.thread_id; if ( @@ -36,10 +40,10 @@ function ThreadList({ itemText = getContentString(firstMessage.content); } return ( -
+
); @@ -58,15 +62,16 @@ function ThreadList({ function ThreadHistoryLoading() { return ( -
+
{Array.from({ length: 30 }).map((_, i) => ( - + ))}
); } export default function ThreadHistory() { + const isLargeScreen = useMediaQuery("(min-width: 1024px)"); const [chatHistoryOpen, setChatHistoryOpen] = useQueryParam( "chatHistoryOpen", BooleanParam, @@ -86,25 +91,44 @@ export default function ThreadHistory() { return ( <> -
-

Thread History

+
+
+ +

+ Thread History +

+
{threadsLoading ? ( ) : ( )}
- - - - Thread History - - setChatHistoryOpen((o) => !o)} - /> - - +
+ { + if (isLargeScreen) return; + setChatHistoryOpen(open); + }} + > + + + Thread History + + setChatHistoryOpen((o) => !o)} + /> + + +
); } diff --git a/src/components/thread/index.tsx b/src/components/thread/index.tsx index 3ffb8af..cadecf5 100644 --- a/src/components/thread/index.tsx +++ b/src/components/thread/index.tsx @@ -1,5 +1,6 @@ import { v4 as uuidv4 } from "uuid"; import { ReactNode, useEffect, useRef } from "react"; +import { motion } from "framer-motion"; import { cn } from "@/lib/utils"; import { useStreamContext } from "@/providers/Stream"; import { useState, FormEvent } from "react"; @@ -24,6 +25,7 @@ import { BooleanParam, StringParam, useQueryParam } from "use-query-params"; import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom"; import ThreadHistory from "./history"; import { toast } from "sonner"; +import { useMediaQuery } from "@/hooks/useMediaQuery"; function StickyToBottomContent(props: { content: ReactNode; @@ -65,12 +67,13 @@ function ScrollToBottom(props: { className?: string }) { export function Thread() { const [threadId, setThreadId] = useQueryParam("threadId", StringParam); - const [_, setChatHistoryOpen] = useQueryParam( + const [chatHistoryOpen, setChatHistoryOpen] = useQueryParam( "chatHistoryOpen", BooleanParam, ); const [input, setInput] = useState(""); const [firstTokenReceived, setFirstTokenReceived] = useState(false); + const isLargeScreen = useMediaQuery("(min-width: 1024px)"); const stream = useStreamContext(); const messages = stream.messages; @@ -166,16 +169,72 @@ export function Thread() { return (
- -
+ +
+ +
+
+
+ + {!chatStarted && ( +
+ {(!chatHistoryOpen || !isLargeScreen) && ( + + )} +
+ )} {chatStarted && (
-
+
+ {(!chatHistoryOpen || !isLargeScreen) && ( + + )} -
-
+
-
+
); } diff --git a/src/hooks/useMediaQuery.tsx b/src/hooks/useMediaQuery.tsx new file mode 100644 index 0000000..7e1468c --- /dev/null +++ b/src/hooks/useMediaQuery.tsx @@ -0,0 +1,16 @@ +import { useEffect, useState } from "react"; + +export function useMediaQuery(query: string) { + const [matches, setMatches] = useState(false); + + useEffect(() => { + const media = window.matchMedia(query); + setMatches(media.matches); + + const listener = (e: MediaQueryListEvent) => setMatches(e.matches); + media.addEventListener("change", listener); + return () => media.removeEventListener("change", listener); + }, [query]); + + return matches; +} diff --git a/src/index.css b/src/index.css index 2d4d0d8..3882f6f 100644 --- a/src/index.css +++ b/src/index.css @@ -118,9 +118,11 @@ * { @apply border-border outline-ring/50; } + body { @apply bg-background text-foreground; } + :root { --chart-1: 12 76% 61%; --chart-2: 173 58% 39%; diff --git a/tailwind.config.js b/tailwind.config.js index f49a279..2a0e74d 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,7 +1,11 @@ /** @type {import('tailwindcss').Config} */ module.exports = { darkMode: ["class"], - content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"], + content: [ + "./index.html", + "./src/**/*.{ts,tsx,js,jsx}", + "./agent/**/*.{ts,tsx,js,jsx}", + ], theme: { extend: { borderRadius: { From e541f93bdec54f953c68f60fd557b614f963c599 Mon Sep 17 00:00:00 2001 From: bracesproul Date: Thu, 6 Mar 2025 16:05:58 -0800 Subject: [PATCH 02/35] make hella smooth --- src/components/thread/history/index.tsx | 1 - src/components/thread/index.tsx | 36 ++++++++++++++++--------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/components/thread/history/index.tsx b/src/components/thread/history/index.tsx index 7d0fbec..cb23a7b 100644 --- a/src/components/thread/history/index.tsx +++ b/src/components/thread/history/index.tsx @@ -12,7 +12,6 @@ import { SheetTitle, } from "@/components/ui/sheet"; import { Skeleton } from "@/components/ui/skeleton"; -import { cn } from "@/lib/utils"; import { PanelRightOpen } from "lucide-react"; import { useMediaQuery } from "@/hooks/useMediaQuery"; diff --git a/src/components/thread/index.tsx b/src/components/thread/index.tsx index cadecf5..2107630 100644 --- a/src/components/thread/index.tsx +++ b/src/components/thread/index.tsx @@ -211,7 +211,7 @@ export function Thread() { } > {!chatStarted && ( -
+
{(!chatHistoryOpen || !isLargeScreen) && ( - )} - + )} +
+ setThreadId(null)} + animate={{ + marginLeft: !chatHistoryOpen ? 48 : 0, + }} + transition={{ + type: "spring", + stiffness: 300, + damping: 30, + }} > LangGraph Chat - +
Date: Thu, 6 Mar 2025 16:07:50 -0800 Subject: [PATCH 03/35] cr --- src/components/thread/history/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/thread/history/index.tsx b/src/components/thread/history/index.tsx index cb23a7b..f0e3169 100644 --- a/src/components/thread/history/index.tsx +++ b/src/components/thread/history/index.tsx @@ -91,7 +91,7 @@ export default function ThreadHistory() { return ( <>
-
+

Thread History diff --git a/src/components/thread/index.tsx b/src/components/thread/index.tsx index fab269a..710144b 100644 --- a/src/components/thread/index.tsx +++ b/src/components/thread/index.tsx @@ -218,7 +218,7 @@ export function Thread() { variant="ghost" onClick={() => setChatHistoryOpen((p) => !p)} > - + )}

@@ -233,7 +233,7 @@ export function Thread() { variant="ghost" onClick={() => setChatHistoryOpen((p) => !p)} > - + )}
@@ -251,7 +251,7 @@ export function Thread() { > - LangGraph Chat + Chat LangGraph
@@ -309,7 +309,7 @@ export function Thread() {

- LangGraph Chat + Chat LangGraph

)} diff --git a/src/providers/Stream.tsx b/src/providers/Stream.tsx index bd2911a..f1b9027 100644 --- a/src/providers/Stream.tsx +++ b/src/providers/Stream.tsx @@ -94,11 +94,11 @@ export const StreamProvider: React.FC<{ children: ReactNode }> = ({

- LangGraph Chat + Chat LangGraph

- Welcome to LangGraph Chat! Before you get started, you need to + Welcome to Chat LangGraph! Before you get started, you need to enter the URL of the deployment and the assistant / graph ID.

From 9fc221e88a7c9394da1b66e8f891e763943df54a Mon Sep 17 00:00:00 2001 From: Tat Dat Duong Date: Fri, 7 Mar 2025 15:18:57 +0100 Subject: [PATCH 18/35] Support multiline input --- src/components/thread/index.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/thread/index.tsx b/src/components/thread/index.tsx index 710144b..a1fa7e9 100644 --- a/src/components/thread/index.tsx +++ b/src/components/thread/index.tsx @@ -4,7 +4,6 @@ import { motion } from "framer-motion"; import { cn } from "@/lib/utils"; import { useStreamContext } from "@/providers/Stream"; import { useState, FormEvent } from "react"; -import { Input } from "../ui/input"; import { Button } from "../ui/button"; import { Checkpoint, Message } from "@langchain/langgraph-sdk"; import { AssistantMessage, AssistantMessageLoading } from "./messages/ai"; @@ -316,17 +315,24 @@ export function Thread() { -
+
- setInput(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter" && !e.shiftKey && !e.metaKey) { + e.preventDefault(); + const el = e.target as HTMLElement | undefined; + const form = el?.closest("form"); + form?.requestSubmit(); + } + }} placeholder="Type your message..." - className="px-4 py-6 border-none bg-transparent shadow-none ring-0 outline-none focus:outline-none focus:ring-0" + className="p-3.5 pb-0 border-none bg-transparent field-sizing-content shadow-none ring-0 outline-none focus:outline-none focus:ring-0 resize-none" />
@@ -338,6 +344,7 @@ export function Thread() { ) : (