diff --git a/package-lock.json b/package-lock.json index cdd2bb0..3772f46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,7 @@ "": { "name": "mcp-app-demo", "dependencies": { + "@pomerium/js-sdk": "^1.1.0", "@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", @@ -1360,6 +1361,15 @@ "node": ">=14" } }, + "node_modules/@pomerium/js-sdk": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pomerium/js-sdk/-/js-sdk-1.1.0.tgz", + "integrity": "sha512-fz6rzaeIjRgOKqB8XoNnDpjfGUaaNl6MwWq15ms4msGowkrsZyX3qk+r85r3QsfC29YAVGER2UdsHGNdNNn1Fg==", + "license": "MIT", + "dependencies": { + "jose": "^5.2.0" + } + }, "node_modules/@poppinss/colors": { "version": "4.1.4", "license": "MIT", @@ -8058,6 +8068,15 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/jose": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.10.0.tgz", + "integrity": "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "license": "MIT" diff --git a/package.json b/package.json index 8ee8e74..feb512b 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "check": "prettier --write . && eslint --fix" }, "dependencies": { + "@pomerium/js-sdk": "^1.1.0", "@radix-ui/react-alert-dialog": "^1.1.14", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx index a59d6f0..4a9a36c 100644 --- a/src/components/Chat.tsx +++ b/src/components/Chat.tsx @@ -8,6 +8,7 @@ import type { Message } from 'ai' import { type Servers } from '../lib/schemas' import { ToolCallMessage } from './ToolCallMessage' import { useModel } from '../contexts/ModelContext' +import { useUser } from '../contexts/UserContext' // Streamed event type type StreamEvent = @@ -31,14 +32,15 @@ export function Chat() { const [streamBuffer, setStreamBuffer] = useState([]) const [streaming, setStreaming] = useState(false) const { selectedModel } = useModel() + const { user } = useUser() const initialMessage = useMemo( () => ({ id: generateMessageId(), - content: "👋 Hello! I'm your AI assistant. How can I help you today?", + content: `Hello ${user?.name?.split(' ')[0] ?? 'there'}! I'm your AI assistant. How can I help you today?`, role: 'assistant', }), - [], + [user?.name], ) // Create a memoized body object that updates when selectedServers or model change diff --git a/src/components/Header.tsx b/src/components/Header.tsx index d5b4bc8..fc03cf4 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -12,9 +12,11 @@ import { ThemeToggle } from './ThemeToggle' import { Button } from './ui/button' import { ModelSelect } from './ModelSelect' import { useModel } from '../contexts/ModelContext' +import { useUser } from '../contexts/UserContext' const Header: React.FC = () => { const { selectedModel, setSelectedModel } = useModel() + const { user, isLoading } = useUser() const [isDark, setIsDark] = useState(false) useEffect(() => { @@ -46,7 +48,7 @@ const Header: React.FC = () => {

User Account

- user@example.com + {isLoading ? 'Loading...' : user?.email}

@@ -80,17 +82,26 @@ const Header: React.FC = () => {
-

User Account

+

{user?.name}

- user@example.com + {user?.email}

diff --git a/src/contexts/UserContext.tsx b/src/contexts/UserContext.tsx new file mode 100644 index 0000000..cd625e1 --- /dev/null +++ b/src/contexts/UserContext.tsx @@ -0,0 +1,48 @@ +import { createContext, useContext } from 'react' +import type { ReactNode } from 'react' +import { getBrowserUser } from '@pomerium/js-sdk' +import { useQuery } from '@tanstack/react-query' +import type { UserInfo } from 'node_modules/@pomerium/js-sdk/lib/esm/types/utils' + +type UserContextType = { + user: UserInfo | undefined + isLoading: boolean + error: unknown +} + +const UserContext = createContext(undefined) + +async function fetchUserInfo(): Promise { + const userInfo = await getBrowserUser() + + return { + email: userInfo.email ?? '', + name: userInfo.name ?? '', + picture: userInfo.picture as string, + } +} + +export function UserProvider({ children }: { children: ReactNode }) { + const { + data: user, + isLoading, + error, + } = useQuery({ + queryKey: ['user'], + queryFn: fetchUserInfo, + }) + + return ( + + {children} + + ) +} + +export function useUser() { + const context = useContext(UserContext) + if (context === undefined) { + throw new Error('useUser must be used within a UserProvider') + } + return context +} diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx index 8f288c8..7a78605 100644 --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -7,6 +7,7 @@ import { } from '@tanstack/react-router' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { ModelProvider } from '../contexts/ModelContext' +import { UserProvider } from '../contexts/UserContext' import appCss from '../styles.css?url' import Header from '../components/Header' @@ -59,11 +60,13 @@ export const Route = createRootRoute({ component: () => ( - - - - - + + + + + + + ), })