diff --git a/src/components/ui/password-input.tsx b/src/components/ui/password-input.tsx new file mode 100644 index 0000000..bf62a41 --- /dev/null +++ b/src/components/ui/password-input.tsx @@ -0,0 +1,54 @@ +"use client"; + +import * as React from "react"; + +import { cn } from "@/lib/utils"; +import { Input } from "./input"; +import { Button } from "./button"; +import { EyeIcon, EyeOffIcon } from "lucide-react"; + +export const PasswordInput = React.forwardRef< + HTMLInputElement, + React.ComponentProps<"input"> +>(({ className, ...props }, ref) => { + const [showPassword, setShowPassword] = React.useState(false); + + return ( +
+ + + + {/* hides browsers password toggles */} + +
+ ); +}); + +PasswordInput.displayName = "PasswordInput"; diff --git a/src/providers/Stream.tsx b/src/providers/Stream.tsx index a7a5173..eac16b5 100644 --- a/src/providers/Stream.tsx +++ b/src/providers/Stream.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useContext, ReactNode } from "react"; +import React, { createContext, useContext, ReactNode, useState } from "react"; import { useStream } from "@langchain/langgraph-sdk/react"; import { type Message } from "@langchain/langgraph-sdk"; import type { @@ -11,6 +11,7 @@ import { Button } from "@/components/ui/button"; import { LangGraphLogoSVG } from "@/components/icons/langgraph"; import { Label } from "@/components/ui/label"; import { ArrowRight } from "lucide-react"; +import { PasswordInput } from "@/components/ui/password-input"; const useTypedStream = useStream< { messages: Message[]; ui: UIMessage[] }, @@ -28,16 +29,19 @@ const StreamContext = createContext(undefined); const StreamSession = ({ children, + apiKey, apiUrl, assistantId, }: { children: ReactNode; + apiKey: string | null; apiUrl: string; assistantId: string; }) => { const [threadId, setThreadId] = useQueryParam("threadId", StringParam); const streamValue = useTypedStream({ apiUrl, + apiKey: apiKey ?? undefined, assistantId, threadId: threadId ?? null, onThreadId: setThreadId, @@ -54,6 +58,21 @@ export const StreamProvider: React.FC<{ children: ReactNode }> = ({ children, }) => { const [apiUrl, setApiUrl] = useQueryParam("apiUrl", StringParam); + const [apiKey, _setApiKey] = useState(() => { + try { + const key = window.localStorage.getItem("lg:chat:apiKey"); + return key || null; + } catch { + // pass + } + return null; + }); + + const setApiKey = (key: string) => { + window.localStorage.setItem("lg:chat:apiKey", key); + _setApiKey(key); + }; + const [assistantId, setAssistantId] = useQueryParam( "assistantId", StringParam, @@ -61,7 +80,7 @@ export const StreamProvider: React.FC<{ children: ReactNode }> = ({ if (!apiUrl || !assistantId) { return ( -
+
@@ -83,10 +102,11 @@ export const StreamProvider: React.FC<{ children: ReactNode }> = ({ const formData = new FormData(form); const apiUrl = formData.get("apiUrl") as string; const assistantId = formData.get("assistantId") as string; + const apiKey = formData.get("apiKey") as string; setApiUrl(apiUrl); + setApiKey(apiKey); setAssistantId(assistantId); - console.log({ apiUrl, assistantId }); form.reset(); }} @@ -103,7 +123,6 @@ export const StreamProvider: React.FC<{ children: ReactNode }> = ({ name="apiUrl" className="bg-background" defaultValue={apiUrl ?? "http://localhost:2024"} - onChange={(e) => setApiUrl(e.target.value)} />
@@ -119,7 +138,21 @@ export const StreamProvider: React.FC<{ children: ReactNode }> = ({ name="assistantId" className="bg-background" defaultValue={assistantId ?? "agent"} - onChange={(e) => setAssistantId(e.target.value)} + /> +
+ +
+ +

+ This value is stored in your browser's local storage and is only + used to authenticate requests sent to your LangGraph server. +

+
@@ -136,7 +169,7 @@ export const StreamProvider: React.FC<{ children: ReactNode }> = ({ } return ( - + {children} );