diff --git a/package.json b/package.json index b4ac49a..951f629 100644 --- a/package.json +++ b/package.json @@ -36,9 +36,11 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-markdown": "^10.0.1", + "react-router-dom": "^6.17.0", "remark-gfm": "^4.0.1", "tailwind-merge": "^3.0.2", "tailwindcss-animate": "^1.0.7", + "use-query-params": "^2.2.1", "uuid": "^11.0.5", "zod": "^3.24.2" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d688263..9a476b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,6 +84,9 @@ importers: react-markdown: specifier: ^10.0.1 version: 10.0.1(@types/react@19.0.10)(react@19.0.0) + react-router-dom: + specifier: ^6.17.0 + version: 6.30.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) remark-gfm: specifier: ^4.0.1 version: 4.0.1 @@ -93,6 +96,9 @@ importers: tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@4.0.9) + use-query-params: + specifier: ^2.2.1 + version: 2.2.1(react-dom@19.0.0(react@19.0.0))(react-router-dom@6.30.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0) uuid: specifier: ^11.0.5 version: 11.1.0 @@ -1420,6 +1426,13 @@ packages: integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==, } + "@remix-run/router@1.23.0": + resolution: + { + integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==, + } + engines: { node: ">=14.0.0" } + "@rollup/rollup-android-arm-eabi@4.34.8": resolution: { @@ -4193,6 +4206,25 @@ packages: "@types/react": optional: true + react-router-dom@6.30.0: + resolution: + { + integrity: sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==, + } + engines: { node: ">=14.0.0" } + peerDependencies: + react: ">=16.8" + react-dom: ">=16.8" + + react-router@6.30.0: + resolution: + { + integrity: sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==, + } + engines: { node: ">=14.0.0" } + peerDependencies: + react: ">=16.8" + react-style-singleton@2.2.3: resolution: { @@ -4361,6 +4393,12 @@ packages: engines: { node: ">=10" } hasBin: true + serialize-query-params@2.0.2: + resolution: + { + integrity: sha512-1chMo1dST4pFA9RDXAtF0Rbjaut4is7bzFbI1Z26IuMub68pNCILku85aYmeFhvnY//BXUPUhoRMjYcsT93J/Q==, + } + shebang-command@2.0.0: resolution: { @@ -4746,6 +4784,22 @@ packages: "@types/react": optional: true + use-query-params@2.2.1: + resolution: + { + integrity: sha512-i6alcyLB8w9i3ZK3caNftdb+UnbfBRNPDnc89CNQWkGRmDrm/gfydHvMBfVsQJRq3NoHOM2dt/ceBWG2397v1Q==, + } + peerDependencies: + "@reach/router": ^1.2.1 + react: ">=16.8.0" + react-dom: ">=16.8.0" + react-router-dom: ">=5" + peerDependenciesMeta: + "@reach/router": + optional: true + react-router-dom: + optional: true + use-sidecar@1.1.3: resolution: { @@ -5797,6 +5851,8 @@ snapshots: "@radix-ui/rect@1.1.0": {} + "@remix-run/router@1.23.0": {} + "@rollup/rollup-android-arm-eabi@4.34.8": optional: true @@ -7583,6 +7639,18 @@ snapshots: optionalDependencies: "@types/react": 19.0.10 + react-router-dom@6.30.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + "@remix-run/router": 1.23.0 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-router: 6.30.0(react@19.0.0) + + react-router@6.30.0(react@19.0.0): + dependencies: + "@remix-run/router": 1.23.0 + react: 19.0.0 + react-style-singleton@2.2.3(@types/react@19.0.10)(react@19.0.0): dependencies: get-nonce: 1.0.1 @@ -7701,6 +7769,8 @@ snapshots: semver@7.7.1: {} + serialize-query-params@2.0.2: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -7911,6 +7981,14 @@ snapshots: optionalDependencies: "@types/react": 19.0.10 + use-query-params@2.2.1(react-dom@19.0.0(react@19.0.0))(react-router-dom@6.30.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0): + dependencies: + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + serialize-query-params: 2.0.2 + optionalDependencies: + react-router-dom: 6.30.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + use-sidecar@1.1.3(@types/react@19.0.10)(react@19.0.0): dependencies: detect-node-es: 1.1.0 diff --git a/src/components/icons/langgraph.tsx b/src/components/icons/langgraph.tsx index c23aaeb..826927b 100644 --- a/src/components/icons/langgraph.tsx +++ b/src/components/icons/langgraph.tsx @@ -8,8 +8,8 @@ export function LangGraphLogoSVG({ width = 20, height = 20 }) { xmlns="http://www.w3.org/2000/svg" > diff --git a/src/components/thread/index.tsx b/src/components/thread/index.tsx index c71b9f0..2a286ba 100644 --- a/src/components/thread/index.tsx +++ b/src/components/thread/index.tsx @@ -13,23 +13,9 @@ import { ensureToolCallsHaveResponses, } from "@/lib/ensure-tool-responses"; import { LangGraphLogoSVG } from "../icons/langgraph"; - -// const dummyMessages = [ -// { type: "human", content: "Hi! What can you do?" }, -// { -// type: "ai", -// content: `Hello! I can assist you with a variety of tasks, including: - -// 1. **Answering Questions**: I can provide information on a wide range of topics, from science and history to technology and culture. -// 2. **Writing Assistance**: I can help you draft emails, essays, reports, and creative writing pieces. -// 3. **Learning Support**: I can explain concepts, help with homework, and provide study tips. -// 4. **Language Help**: I can assist with translations, grammar, and vocabulary in multiple languages. -// 5. **Recommendations**: I can suggest books, movies, recipes, and more based on your interests. -// 6. **General Advice**: I can offer tips on various subjects, including productivity, wellness, and personal development. - -// If you have something specific in mind, feel free to ask!`, -// }, -// ]; +import { TooltipIconButton } from "./tooltip-icon-button"; +import { SquarePen } from "lucide-react"; +import { StringParam, useQueryParam } from "use-query-params"; function Title({ className }: { className?: string }) { return ( @@ -40,11 +26,26 @@ function Title({ className }: { className?: string }) { ); } +function NewThread() { + const [_, setThreadId] = useQueryParam("threadId", StringParam); + + return ( + setThreadId(null)} + > + + + ); +} + export function Thread() { const [input, setInput] = useState(""); const [firstTokenReceived, setFirstTokenReceived] = useState(false); const stream = useStreamContext(); - // const messages = [...dummyMessages, ...stream.messages]; const messages = stream.messages; const isLoading = stream.isLoading; const prevMessageLength = useRef(0); @@ -105,7 +106,8 @@ export function Thread() { )} {chatStarted && ( -
+
+ </div> )} diff --git a/src/main.tsx b/src/main.tsx index 908912a..ce2eeaf 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2,9 +2,16 @@ import { createRoot } from "react-dom/client"; import "./index.css"; import App from "./App.tsx"; import { StreamProvider } from "./providers/Stream.tsx"; +import { QueryParamProvider } from "use-query-params"; +import { ReactRouter6Adapter } from "use-query-params/adapters/react-router-6"; +import { BrowserRouter } from "react-router-dom"; createRoot(document.getElementById("root")!).render( - <StreamProvider> - <App /> - </StreamProvider>, + <BrowserRouter> + <QueryParamProvider adapter={ReactRouter6Adapter}> + <StreamProvider> + <App /> + </StreamProvider> + </QueryParamProvider> + </BrowserRouter>, ); diff --git a/src/providers/Stream.tsx b/src/providers/Stream.tsx index 0e3e3a4..d8f960e 100644 --- a/src/providers/Stream.tsx +++ b/src/providers/Stream.tsx @@ -1,10 +1,11 @@ import React, { createContext, useContext, ReactNode } from "react"; import { useStream } from "@langchain/langgraph-sdk/react"; -import type { Message } from "@langchain/langgraph-sdk"; +import { type Message } from "@langchain/langgraph-sdk"; import type { UIMessage, RemoveUIMessage, } from "@langchain/langgraph-sdk/react-ui/types"; +import { useQueryParam, StringParam } from "use-query-params"; const useTypedStream = useStream< { messages: Message[]; ui: UIMessage[] }, @@ -23,11 +24,18 @@ const StreamContext = createContext<StreamContextType | undefined>(undefined); export const StreamProvider: React.FC<{ children: ReactNode }> = ({ children, }) => { + const [threadId, setThreadId] = useQueryParam("threadId", StringParam); + const streamValue = useTypedStream({ apiUrl: "http://localhost:2024", assistantId: "agent", + threadId: threadId ?? null, + onThreadId: setThreadId, }); + console.log("threadId", threadId); + console.log("streamValue", streamValue.values); + return ( <StreamContext.Provider value={streamValue}> {children}