├── .cursor └── mcp.json ├── .gitignore ├── apps ├── admin │ ├── .cursorrules │ ├── .gitignore │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── app.config.ts │ ├── components.json │ ├── package.json │ ├── postcss.config.ts │ ├── public │ │ ├── example-guitar-dune.jpg │ │ ├── example-guitar-motherboard.jpg │ │ ├── example-guitar-racing.jpg │ │ ├── example-guitar-steamer-trunk.jpg │ │ ├── example-guitar-steampunk.jpg │ │ ├── example-guitar-underwater.jpg │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ ├── src │ │ ├── api.ts │ │ ├── client.tsx │ │ ├── components │ │ │ └── Header.tsx │ │ ├── demo.index.css │ │ ├── integrations │ │ │ └── tanstack-query │ │ │ │ ├── layout.tsx │ │ │ │ └── root-provider.tsx │ │ ├── lib │ │ │ └── utils.ts │ │ ├── logo.svg │ │ ├── routeTree.gen.ts │ │ ├── router.tsx │ │ ├── routes │ │ │ ├── __root.tsx │ │ │ ├── index.tsx │ │ │ └── orders.tsx │ │ ├── ssr.tsx │ │ ├── styles.css │ │ └── utils │ │ │ └── apis.ts │ └── tsconfig.json ├── frontend │ ├── .cursorrules │ ├── .gitignore │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── app.config.ts │ ├── components.json │ ├── package.json │ ├── postcss.config.ts │ ├── public │ │ ├── example-guitar-dune.jpg │ │ ├── example-guitar-motherboard.jpg │ │ ├── example-guitar-racing.jpg │ │ ├── example-guitar-steamer-trunk.jpg │ │ ├── example-guitar-steampunk.jpg │ │ ├── example-guitar-underwater.jpg │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ ├── src │ │ ├── api.ts │ │ ├── client.tsx │ │ ├── components │ │ │ ├── AIAssistant.tsx │ │ │ ├── GuitarRecommendation.tsx │ │ │ └── Header.tsx │ │ ├── demo.index.css │ │ ├── integrations │ │ │ ├── tanchat │ │ │ │ └── header-user.tsx │ │ │ └── tanstack-query │ │ │ │ ├── layout.tsx │ │ │ │ └── root-provider.tsx │ │ ├── lib │ │ │ └── utils.ts │ │ ├── logo.svg │ │ ├── routeTree.gen.ts │ │ ├── router.tsx │ │ ├── routes │ │ │ ├── __root.tsx │ │ │ ├── chat.tsx │ │ │ ├── guitars │ │ │ │ └── $guitarId.tsx │ │ │ └── index.tsx │ │ ├── ssr.tsx │ │ ├── store │ │ │ └── assistant.ts │ │ ├── styles.css │ │ └── utils │ │ │ ├── ai-tools.ts │ │ │ ├── ai.ts │ │ │ └── apis.ts │ └── tsconfig.json ├── fulfillment-api │ ├── package.json │ ├── server.ts │ └── tsconfig.json ├── mcp-order-server │ ├── package.json │ ├── server-logic.js │ ├── sse-server.js │ └── stdio-server.js └── products-api │ ├── example-guitars.ts │ ├── images │ ├── example-guitar-dune.jpg │ ├── example-guitar-motherboard.jpg │ ├── example-guitar-racing.jpg │ ├── example-guitar-steamer-trunk.jpg │ ├── example-guitar-steampunk.jpg │ └── example-guitar-underwater.jpg │ ├── package.json │ ├── server.ts │ └── tsconfig.json ├── nx.json ├── package.json ├── pnpm-lock.yaml └── pnpm-workspace.yaml /.cursor/mcp.json: -------------------------------------------------------------------------------- 1 | { 2 | "mcpServers": { 3 | "nx-mcp": { 4 | "url": "http://localhost:9533/sse" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | **/node_modules 4 | .nx -------------------------------------------------------------------------------- /apps/admin/.cursorrules: -------------------------------------------------------------------------------- 1 | # shadcn instructions 2 | 3 | Use the latest version of Shadcn to install new components, like this command to add a button component: 4 | 5 | ```bash 6 | pnpx shadcn@latest add button 7 | ``` 8 | -------------------------------------------------------------------------------- /apps/admin/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | .output 7 | .vinxi 8 | -------------------------------------------------------------------------------- /apps/admin/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.watcherExclude": { 3 | "**/routeTree.gen.ts": true 4 | }, 5 | "search.exclude": { 6 | "**/routeTree.gen.ts": true 7 | }, 8 | "files.readonlyInclude": { 9 | "**/routeTree.gen.ts": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/admin/README.md: -------------------------------------------------------------------------------- 1 | Welcome to your new TanStack app! 2 | 3 | # Getting Started 4 | 5 | To run this application: 6 | 7 | ```bash 8 | pnpm install 9 | pnpm start 10 | ``` 11 | 12 | # Building For Production 13 | 14 | To build this application for production: 15 | 16 | ```bash 17 | pnpm build 18 | ``` 19 | 20 | ## Testing 21 | 22 | This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with: 23 | 24 | ```bash 25 | pnpm test 26 | ``` 27 | 28 | ## Styling 29 | 30 | This project uses [Tailwind CSS](https://tailwindcss.com/) for styling. 31 | 32 | 33 | 34 | 35 | ## Shadcn 36 | 37 | Add components using the latest version of [Shadcn](https://ui.shadcn.com/). 38 | 39 | ```bash 40 | pnpx shadcn@latest add button 41 | ``` 42 | 43 | 44 | # TanStack Chat Application 45 | 46 | Am example chat application built with TanStack Start, TanStack Store, and Claude AI. 47 | 48 | ## .env Updates 49 | 50 | ```env 51 | ANTHROPIC_API_KEY=your_anthropic_api_key 52 | ``` 53 | 54 | ## ✨ Features 55 | 56 | ### AI Capabilities 57 | - 🤖 Powered by Claude 3.5 Sonnet 58 | - 📝 Rich markdown formatting with syntax highlighting 59 | - 🎯 Customizable system prompts for tailored AI behavior 60 | - 🔄 Real-time message updates and streaming responses (coming soon) 61 | 62 | ### User Experience 63 | - 🎨 Modern UI with Tailwind CSS and Lucide icons 64 | - 🔍 Conversation management and history 65 | - 🔐 Secure API key management 66 | - 📋 Markdown rendering with code highlighting 67 | 68 | ### Technical Features 69 | - 📦 Centralized state management with TanStack Store 70 | - 🔌 Extensible architecture for multiple AI providers 71 | - 🛠️ TypeScript for type safety 72 | 73 | ## Architecture 74 | 75 | ### Tech Stack 76 | - **Frontend Framework**: TanStack Start 77 | - **Routing**: TanStack Router 78 | - **State Management**: TanStack Store 79 | - **Styling**: Tailwind CSS 80 | - **AI Integration**: Anthropic's Claude API 81 | 82 | 83 | ## Routing 84 | This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a file based router. Which means that the routes are managed as files in `src/routes`. 85 | 86 | ### Adding A Route 87 | 88 | To add a new route to your application just add another a new file in the `./src/routes` directory. 89 | 90 | TanStack will automatically generate the content of the route file for you. 91 | 92 | Now that you have two routes you can use a `Link` component to navigate between them. 93 | 94 | ### Adding Links 95 | 96 | To use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/react-router`. 97 | 98 | ```tsx 99 | import { Link } from "@tanstack/react-router"; 100 | ``` 101 | 102 | Then anywhere in your JSX you can use it like so: 103 | 104 | ```tsx 105 | About 106 | ``` 107 | 108 | This will create a link that will navigate to the `/about` route. 109 | 110 | More information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent). 111 | 112 | ### Using A Layout 113 | 114 | In the File Based Routing setup the layout is located in `src/routes/__root.tsx`. Anything you add to the root route will appear in all the routes. The route content will appear in the JSX where you use the `` component. 115 | 116 | Here is an example layout that includes a header: 117 | 118 | ```tsx 119 | import { Outlet, createRootRoute } from '@tanstack/react-router' 120 | import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' 121 | 122 | import { Link } from "@tanstack/react-router"; 123 | 124 | export const Route = createRootRoute({ 125 | component: () => ( 126 | <> 127 |
128 | 132 |
133 | 134 | 135 | 136 | ), 137 | }) 138 | ``` 139 | 140 | The `` component is not required so you can remove it if you don't want it in your layout. 141 | 142 | More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts). 143 | 144 | 145 | ## Data Fetching 146 | 147 | There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered. 148 | 149 | For example: 150 | 151 | ```tsx 152 | const peopleRoute = createRoute({ 153 | getParentRoute: () => rootRoute, 154 | path: "/people", 155 | loader: async () => { 156 | const response = await fetch("https://swapi.dev/api/people"); 157 | return response.json() as Promise<{ 158 | results: { 159 | name: string; 160 | }[]; 161 | }>; 162 | }, 163 | component: () => { 164 | const data = peopleRoute.useLoaderData(); 165 | return ( 166 |
    167 | {data.results.map((person) => ( 168 |
  • {person.name}
  • 169 | ))} 170 |
171 | ); 172 | }, 173 | }); 174 | ``` 175 | 176 | Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters). 177 | 178 | ### React-Query 179 | 180 | React-Query is an excellent addition or alternative to route loading and integrating it into you application is a breeze. 181 | 182 | First add your dependencies: 183 | 184 | ```bash 185 | pnpm add @tanstack/react-query @tanstack/react-query-devtools 186 | ``` 187 | 188 | Next we'll need to create a query client and provider. We recommend putting those in `main.tsx`. 189 | 190 | ```tsx 191 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 192 | 193 | // ... 194 | 195 | const queryClient = new QueryClient(); 196 | 197 | // ... 198 | 199 | if (!rootElement.innerHTML) { 200 | const root = ReactDOM.createRoot(rootElement); 201 | 202 | root.render( 203 | 204 | 205 | 206 | ); 207 | } 208 | ``` 209 | 210 | You can also add TanStack Query Devtools to the root route (optional). 211 | 212 | ```tsx 213 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; 214 | 215 | const rootRoute = createRootRoute({ 216 | component: () => ( 217 | <> 218 | 219 | 220 | 221 | 222 | ), 223 | }); 224 | ``` 225 | 226 | Now you can use `useQuery` to fetch your data. 227 | 228 | ```tsx 229 | import { useQuery } from "@tanstack/react-query"; 230 | 231 | import "./App.css"; 232 | 233 | function App() { 234 | const { data } = useQuery({ 235 | queryKey: ["people"], 236 | queryFn: () => 237 | fetch("https://swapi.dev/api/people") 238 | .then((res) => res.json()) 239 | .then((data) => data.results as { name: string }[]), 240 | initialData: [], 241 | }); 242 | 243 | return ( 244 |
245 |
    246 | {data.map((person) => ( 247 |
  • {person.name}
  • 248 | ))} 249 |
250 |
251 | ); 252 | } 253 | 254 | export default App; 255 | ``` 256 | 257 | You can find out everything you need to know on how to use React-Query in the [React-Query documentation](https://tanstack.com/query/latest/docs/framework/react/overview). 258 | 259 | ## State Management 260 | 261 | Another common requirement for React applications is state management. There are many options for state management in React. TanStack Store provides a great starting point for your project. 262 | 263 | First you need to add TanStack Store as a dependency: 264 | 265 | ```bash 266 | pnpm add @tanstack/store 267 | ``` 268 | 269 | Now let's create a simple counter in the `src/App.tsx` file as a demonstration. 270 | 271 | ```tsx 272 | import { useStore } from "@tanstack/react-store"; 273 | import { Store } from "@tanstack/store"; 274 | import "./App.css"; 275 | 276 | const countStore = new Store(0); 277 | 278 | function App() { 279 | const count = useStore(countStore); 280 | return ( 281 |
282 | 285 |
286 | ); 287 | } 288 | 289 | export default App; 290 | ``` 291 | 292 | One of the many nice features of TanStack Store is the ability to derive state from other state. That derived state will update when the base state updates. 293 | 294 | Let's check this out by doubling the count using derived state. 295 | 296 | ```tsx 297 | import { useStore } from "@tanstack/react-store"; 298 | import { Store, Derived } from "@tanstack/store"; 299 | import "./App.css"; 300 | 301 | const countStore = new Store(0); 302 | 303 | const doubledStore = new Derived({ 304 | fn: () => countStore.state * 2, 305 | deps: [countStore], 306 | }); 307 | doubledStore.mount(); 308 | 309 | function App() { 310 | const count = useStore(countStore); 311 | const doubledCount = useStore(doubledStore); 312 | 313 | return ( 314 |
315 | 318 |
Doubled - {doubledCount}
319 |
320 | ); 321 | } 322 | 323 | export default App; 324 | ``` 325 | 326 | We use the `Derived` class to create a new store that is derived from another store. The `Derived` class has a `mount` method that will start the derived store updating. 327 | 328 | Once we've created the derived store we can use it in the `App` component just like we would any other store using the `useStore` hook. 329 | 330 | You can find out everything you need to know on how to use TanStack Store in the [TanStack Store documentation](https://tanstack.com/store/latest). 331 | 332 | # Demo files 333 | 334 | Files prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed. 335 | 336 | # Learn More 337 | 338 | You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com). 339 | -------------------------------------------------------------------------------- /apps/admin/app.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@tanstack/react-start/config' 2 | import viteTsConfigPaths from 'vite-tsconfig-paths' 3 | import tailwindcss from '@tailwindcss/vite' 4 | 5 | export default defineConfig({ 6 | tsr: { 7 | appDirectory: 'src', 8 | }, 9 | vite: { 10 | plugins: [ 11 | // this is the plugin that enables path aliases 12 | viteTsConfigPaths({ 13 | projects: ['./tsconfig.json'], 14 | }), 15 | tailwindcss(), 16 | ], 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /apps/admin/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/styles.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /apps/admin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "admin", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "start": "vinxi start", 7 | "build": "vinxi build", 8 | "serve": "vite preview", 9 | "test": "vitest run", 10 | "dev": "PORT=3001 vinxi dev" 11 | }, 12 | "dependencies": { 13 | "@ai-sdk/anthropic": "^1.1.17", 14 | "@ai-sdk/react": "^1.1.23", 15 | "@faker-js/faker": "^9.6.0", 16 | "@modelcontextprotocol/sdk": "^1.7.0", 17 | "@tailwindcss/postcss": "^4.0.7", 18 | "@tailwindcss/vite": "^4.0.6", 19 | "@tanstack/match-sorter-utils": "^8.19.4", 20 | "@tanstack/react-query": "^5.66.5", 21 | "@tanstack/react-query-devtools": "^5.66.5", 22 | "@tanstack/react-router": "^1.114.3", 23 | "@tanstack/react-router-devtools": "^1.114.3", 24 | "@tanstack/react-router-with-query": "^1.114.3", 25 | "@tanstack/react-start": "^1.114.3", 26 | "@tanstack/react-store": "^0.7.0", 27 | "@tanstack/react-table": "^8.21.2", 28 | "@tanstack/router-plugin": "^1.114.3", 29 | "@tanstack/store": "^0.7.0", 30 | "ai": "^4.1.61", 31 | "class-variance-authority": "^0.7.1", 32 | "clsx": "^2.1.1", 33 | "date-fns": "^4.1.0", 34 | "highlight.js": "^11.11.1", 35 | "lucide-react": "^0.475.0", 36 | "postcss": "^8.5.2", 37 | "react": "^19.0.0", 38 | "react-dom": "^19.0.0", 39 | "react-markdown": "^9.0.1", 40 | "rehype-highlight": "^7.0.0", 41 | "rehype-raw": "^7.0.0", 42 | "rehype-sanitize": "^6.0.0", 43 | "remark-gfm": "^4.0.1", 44 | "tailwind-merge": "^3.0.2", 45 | "tailwindcss": "^4.0.6", 46 | "tailwindcss-animate": "^1.0.7", 47 | "vinxi": "^0.5.3", 48 | "vite-tsconfig-paths": "^5.1.4", 49 | "zod": "^3.24.2" 50 | }, 51 | "devDependencies": { 52 | "@testing-library/dom": "^10.4.0", 53 | "@testing-library/react": "^16.2.0", 54 | "@types/node": "^22.13.10", 55 | "@types/react": "^19.0.8", 56 | "@types/react-dom": "^19.0.3", 57 | "@vitejs/plugin-react": "^4.3.4", 58 | "jsdom": "^26.0.0", 59 | "typescript": "^5.7.2", 60 | "vite": "^6.1.0", 61 | "vitest": "^3.0.5", 62 | "web-vitals": "^4.2.4" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /apps/admin/postcss.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | "@tailwindcss/postcss": {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /apps/admin/public/example-guitar-dune.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/mcp-client-and-server/d76c6cf352e03e31cba8397bf28f33103e7d7c9a/apps/admin/public/example-guitar-dune.jpg -------------------------------------------------------------------------------- /apps/admin/public/example-guitar-motherboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/mcp-client-and-server/d76c6cf352e03e31cba8397bf28f33103e7d7c9a/apps/admin/public/example-guitar-motherboard.jpg -------------------------------------------------------------------------------- /apps/admin/public/example-guitar-racing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/mcp-client-and-server/d76c6cf352e03e31cba8397bf28f33103e7d7c9a/apps/admin/public/example-guitar-racing.jpg -------------------------------------------------------------------------------- /apps/admin/public/example-guitar-steamer-trunk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/mcp-client-and-server/d76c6cf352e03e31cba8397bf28f33103e7d7c9a/apps/admin/public/example-guitar-steamer-trunk.jpg -------------------------------------------------------------------------------- /apps/admin/public/example-guitar-steampunk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/mcp-client-and-server/d76c6cf352e03e31cba8397bf28f33103e7d7c9a/apps/admin/public/example-guitar-steampunk.jpg -------------------------------------------------------------------------------- /apps/admin/public/example-guitar-underwater.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/mcp-client-and-server/d76c6cf352e03e31cba8397bf28f33103e7d7c9a/apps/admin/public/example-guitar-underwater.jpg -------------------------------------------------------------------------------- /apps/admin/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/mcp-client-and-server/d76c6cf352e03e31cba8397bf28f33103e7d7c9a/apps/admin/public/favicon.ico -------------------------------------------------------------------------------- /apps/admin/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/mcp-client-and-server/d76c6cf352e03e31cba8397bf28f33103e7d7c9a/apps/admin/public/logo192.png -------------------------------------------------------------------------------- /apps/admin/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/mcp-client-and-server/d76c6cf352e03e31cba8397bf28f33103e7d7c9a/apps/admin/public/logo512.png -------------------------------------------------------------------------------- /apps/admin/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "TanStack App", 3 | "name": "Create TanStack App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /apps/admin/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /apps/admin/src/api.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createStartAPIHandler, 3 | defaultAPIFileRouteHandler, 4 | } from "@tanstack/react-start/api"; 5 | 6 | export default createStartAPIHandler(defaultAPIFileRouteHandler); 7 | -------------------------------------------------------------------------------- /apps/admin/src/client.tsx: -------------------------------------------------------------------------------- 1 | import { hydrateRoot } from 'react-dom/client' 2 | import { StartClient } from '@tanstack/react-start' 3 | 4 | import { createRouter } from './router' 5 | 6 | const router = createRouter() 7 | 8 | hydrateRoot(document, ) 9 | -------------------------------------------------------------------------------- /apps/admin/src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "@tanstack/react-router"; 2 | 3 | export default function Header() { 4 | return ( 5 |
6 | 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /apps/admin/src/demo.index.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | @import "highlight.js/styles/github-dark.css"; 3 | 4 | /* Custom scrollbar styles */ 5 | ::-webkit-scrollbar { 6 | width: 8px; 7 | } 8 | 9 | ::-webkit-scrollbar-track { 10 | background: transparent; 11 | } 12 | 13 | ::-webkit-scrollbar-thumb { 14 | background-color: rgba(156, 163, 175, 0.5); 15 | border-radius: 4px; 16 | } 17 | 18 | ::-webkit-scrollbar-thumb:hover { 19 | background-color: rgba(156, 163, 175, 0.7); 20 | } 21 | 22 | /* Smooth transitions for dark mode */ 23 | html { 24 | transition: background-color 0.3s ease; 25 | } 26 | 27 | /* Markdown content styles */ 28 | .prose { 29 | max-width: none; 30 | color: #e5e7eb; /* text-gray-200 */ 31 | } 32 | 33 | /* .prose p { 34 | margin-top: 1.25em; 35 | margin-bottom: 1.25em; 36 | } */ 37 | 38 | .prose code { 39 | color: #e5e7eb; 40 | background-color: rgba(31, 41, 55, 0.5); 41 | padding: 0.2em 0.4em; 42 | border-radius: 0.375rem; 43 | font-size: 0.875em; 44 | } 45 | 46 | .prose pre { 47 | background-color: rgba(31, 41, 55, 0.5); 48 | border-radius: 0.5rem; 49 | padding: 1rem; 50 | margin: 1.25em 0; 51 | overflow-x: auto; 52 | } 53 | 54 | .prose pre code { 55 | background-color: transparent; 56 | padding: 0; 57 | border-radius: 0; 58 | color: inherit; 59 | } 60 | 61 | .prose h1, .prose h2, .prose h3, .prose h4 { 62 | color: #f9fafb; /* text-gray-50 */ 63 | /* margin-top: 2em; */ 64 | /* margin-bottom: 1em; */ 65 | } 66 | 67 | .prose ul, .prose ol { 68 | margin-top: 1.25em; 69 | margin-bottom: 1.25em; 70 | padding-left: 1.625em; 71 | } 72 | 73 | .prose li { 74 | margin-top: 0.5em; 75 | margin-bottom: 0.5em; 76 | } 77 | 78 | .prose blockquote { 79 | border-left-color: #f97316; /* orange-500 */ 80 | background-color: rgba(249, 115, 22, 0.1); 81 | padding: 1em; 82 | margin: 1.25em 0; 83 | border-radius: 0.5rem; 84 | } 85 | 86 | .prose hr { 87 | border-color: rgba(249, 115, 22, 0.2); 88 | margin: 2em 0; 89 | } 90 | 91 | .prose a { 92 | color: #f97316; /* orange-500 */ 93 | text-decoration: underline; 94 | text-decoration-thickness: 0.1em; 95 | text-underline-offset: 0.2em; 96 | } 97 | 98 | .prose a:hover { 99 | color: #fb923c; /* orange-400 */ 100 | } 101 | 102 | .prose table { 103 | width: 100%; 104 | border-collapse: collapse; 105 | margin: 1.25em 0; 106 | } 107 | 108 | .prose th, .prose td { 109 | padding: 0.75em; 110 | border: 1px solid rgba(249, 115, 22, 0.2); 111 | } 112 | 113 | .prose th { 114 | background-color: rgba(249, 115, 22, 0.1); 115 | font-weight: 600; 116 | } 117 | 118 | /* Message transition animations */ 119 | .message-enter { 120 | opacity: 0; 121 | transform: translateY(10px); 122 | } 123 | 124 | .message-enter-active { 125 | opacity: 1; 126 | transform: translateY(0); 127 | transition: opacity 300ms, transform 300ms; 128 | } 129 | 130 | .message-exit { 131 | opacity: 1; 132 | } 133 | 134 | .message-exit-active { 135 | opacity: 0; 136 | transition: opacity 300ms; 137 | } 138 | 139 | /* Add/update these styles to match AI formatting capabilities */ 140 | .prose h1 { 141 | font-size: 2em; 142 | /* margin-top: 1em; */ 143 | margin-bottom: 0.5em; 144 | } 145 | 146 | .prose h2 { 147 | font-size: 1.5em; 148 | margin-top: 1em; 149 | margin-bottom: 0.5em; 150 | } 151 | 152 | .prose h3 { 153 | font-size: 1.25em; 154 | margin-top: 1em; 155 | margin-bottom: 0.5em; 156 | } 157 | 158 | .prose ul { 159 | list-style-type: disc; 160 | padding-left: 1.5em; 161 | } 162 | 163 | .prose ol { 164 | list-style-type: decimal; 165 | padding-left: 1.5em; 166 | } 167 | 168 | .prose table { 169 | width: 100%; 170 | border-collapse: collapse; 171 | margin: 1em 0; 172 | } 173 | 174 | .prose th, 175 | .prose td { 176 | border: 1px solid rgba(249, 115, 22, 0.2); 177 | padding: 0.5em; 178 | } 179 | 180 | .prose th { 181 | background-color: rgba(249, 115, 22, 0.1); 182 | } 183 | 184 | .prose strong { 185 | color: #f9fafb; /* text-gray-50 */ 186 | font-weight: 600; 187 | } 188 | 189 | .prose em { 190 | font-style: italic; 191 | } 192 | 193 | .prose blockquote { 194 | border-left: 4px solid #f97316; /* orange-500 */ 195 | padding-left: 1em; 196 | margin: 1em 0; 197 | color: #d1d5db; /* text-gray-300 */ 198 | } 199 | 200 | /* Ensure code blocks match the AI's formatting */ 201 | .prose code { 202 | color: #e5e7eb; 203 | background-color: rgba(31, 41, 55, 0.5); 204 | padding: 0.2em 0.4em; 205 | border-radius: 0.375rem; 206 | font-size: 0.875em; 207 | } 208 | 209 | .prose pre { 210 | background-color: rgba(31, 41, 55, 0.5); 211 | border-radius: 0.5rem; 212 | padding: 1rem; 213 | margin: 1em 0; 214 | } 215 | 216 | .prose pre code { 217 | background-color: transparent; 218 | padding: 0; 219 | border-radius: 0; 220 | } -------------------------------------------------------------------------------- /apps/admin/src/integrations/tanstack-query/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ReactQueryDevtools } from '@tanstack/react-query-devtools' 2 | 3 | export default function LayoutAddition() { 4 | return 5 | } 6 | -------------------------------------------------------------------------------- /apps/admin/src/integrations/tanstack-query/root-provider.tsx: -------------------------------------------------------------------------------- 1 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query' 2 | 3 | const queryClient = new QueryClient() 4 | 5 | export function getContext() { 6 | return { 7 | queryClient, 8 | } 9 | } 10 | 11 | export function Provider({ children }: { children: React.ReactNode }) { 12 | return ( 13 | {children} 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /apps/admin/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /apps/admin/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /apps/admin/src/routeTree.gen.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | // @ts-nocheck 4 | 5 | // noinspection JSUnusedGlobalSymbols 6 | 7 | // This file was automatically generated by TanStack Router. 8 | // You should NOT make any changes in this file as it will be overwritten. 9 | // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. 10 | 11 | // Import Routes 12 | 13 | import { Route as rootRoute } from './routes/__root' 14 | import { Route as OrdersImport } from './routes/orders' 15 | import { Route as IndexImport } from './routes/index' 16 | 17 | // Create/Update Routes 18 | 19 | const OrdersRoute = OrdersImport.update({ 20 | id: '/orders', 21 | path: '/orders', 22 | getParentRoute: () => rootRoute, 23 | } as any) 24 | 25 | const IndexRoute = IndexImport.update({ 26 | id: '/', 27 | path: '/', 28 | getParentRoute: () => rootRoute, 29 | } as any) 30 | 31 | // Populate the FileRoutesByPath interface 32 | 33 | declare module '@tanstack/react-router' { 34 | interface FileRoutesByPath { 35 | '/': { 36 | id: '/' 37 | path: '/' 38 | fullPath: '/' 39 | preLoaderRoute: typeof IndexImport 40 | parentRoute: typeof rootRoute 41 | } 42 | '/orders': { 43 | id: '/orders' 44 | path: '/orders' 45 | fullPath: '/orders' 46 | preLoaderRoute: typeof OrdersImport 47 | parentRoute: typeof rootRoute 48 | } 49 | } 50 | } 51 | 52 | // Create and export the route tree 53 | 54 | export interface FileRoutesByFullPath { 55 | '/': typeof IndexRoute 56 | '/orders': typeof OrdersRoute 57 | } 58 | 59 | export interface FileRoutesByTo { 60 | '/': typeof IndexRoute 61 | '/orders': typeof OrdersRoute 62 | } 63 | 64 | export interface FileRoutesById { 65 | __root__: typeof rootRoute 66 | '/': typeof IndexRoute 67 | '/orders': typeof OrdersRoute 68 | } 69 | 70 | export interface FileRouteTypes { 71 | fileRoutesByFullPath: FileRoutesByFullPath 72 | fullPaths: '/' | '/orders' 73 | fileRoutesByTo: FileRoutesByTo 74 | to: '/' | '/orders' 75 | id: '__root__' | '/' | '/orders' 76 | fileRoutesById: FileRoutesById 77 | } 78 | 79 | export interface RootRouteChildren { 80 | IndexRoute: typeof IndexRoute 81 | OrdersRoute: typeof OrdersRoute 82 | } 83 | 84 | const rootRouteChildren: RootRouteChildren = { 85 | IndexRoute: IndexRoute, 86 | OrdersRoute: OrdersRoute, 87 | } 88 | 89 | export const routeTree = rootRoute 90 | ._addFileChildren(rootRouteChildren) 91 | ._addFileTypes() 92 | 93 | /* ROUTE_MANIFEST_START 94 | { 95 | "routes": { 96 | "__root__": { 97 | "filePath": "__root.tsx", 98 | "children": [ 99 | "/", 100 | "/orders" 101 | ] 102 | }, 103 | "/": { 104 | "filePath": "index.tsx" 105 | }, 106 | "/orders": { 107 | "filePath": "orders.tsx" 108 | } 109 | } 110 | } 111 | ROUTE_MANIFEST_END */ 112 | -------------------------------------------------------------------------------- /apps/admin/src/router.tsx: -------------------------------------------------------------------------------- 1 | import { createRouter as createTanstackRouter } from '@tanstack/react-router' 2 | import { routerWithQueryClient } from '@tanstack/react-router-with-query' 3 | import * as TanstackQuery from './integrations/tanstack-query/root-provider' 4 | 5 | // Import the generated route tree 6 | import { routeTree } from './routeTree.gen' 7 | 8 | import './styles.css' 9 | 10 | // Create a new router instance 11 | export const createRouter = () => { 12 | const router = routerWithQueryClient( 13 | createTanstackRouter({ 14 | routeTree, 15 | context: { 16 | ...TanstackQuery.getContext(), 17 | }, 18 | scrollRestoration: true, 19 | defaultPreloadStaleTime: 0, 20 | }), 21 | TanstackQuery.getContext().queryClient, 22 | ) 23 | 24 | return router 25 | } 26 | 27 | // Register the router instance for type safety 28 | declare module '@tanstack/react-router' { 29 | interface Register { 30 | router: ReturnType 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /apps/admin/src/routes/__root.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Outlet, 3 | HeadContent, 4 | Scripts, 5 | createRootRouteWithContext, 6 | } from '@tanstack/react-router' 7 | import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' 8 | 9 | import Header from '../components/Header' 10 | 11 | import TanstackQueryLayout from '../integrations/tanstack-query/layout' 12 | 13 | import appCss from '../styles.css?url' 14 | 15 | import type { QueryClient } from '@tanstack/react-query' 16 | interface MyRouterContext { 17 | queryClient: QueryClient 18 | } 19 | 20 | export const Route = createRootRouteWithContext()({ 21 | head: () => ({ 22 | meta: [ 23 | { 24 | charSet: 'utf-8', 25 | }, 26 | { 27 | name: 'viewport', 28 | content: 'width=device-width, initial-scale=1', 29 | }, 30 | { 31 | title: 'TanStack Start Starter', 32 | }, 33 | ], 34 | links: [ 35 | { 36 | rel: 'stylesheet', 37 | href: appCss, 38 | }, 39 | ], 40 | }), 41 | 42 | component: () => ( 43 | 44 |
45 | 46 | 47 | 48 | 49 | 50 | 51 | ), 52 | }) 53 | 54 | function RootDocument({ children }: { children: React.ReactNode }) { 55 | return ( 56 | 57 | 58 | 59 | 60 | 61 | {children} 62 | 63 | 64 | 65 | ) 66 | } 67 | -------------------------------------------------------------------------------- /apps/admin/src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import { createFileRoute } from "@tanstack/react-router"; 2 | import { fetchGuitars, fetchInventory } from "../utils/apis"; 3 | 4 | export const Route = createFileRoute("/")({ 5 | component: RouteComponent, 6 | loader: async () => { 7 | const guitars = await fetchGuitars(); 8 | const inventory = await fetchInventory(); 9 | return { guitars, inventory }; 10 | }, 11 | }); 12 | 13 | function RouteComponent() { 14 | const { guitars, inventory } = Route.useLoaderData(); 15 | 16 | return ( 17 |
18 |

19 | Guitar Inventory 20 |

21 | 22 |
23 | {guitars.map((guitar) => { 24 | const stock = 25 | inventory.find((item) => item.guitarId === guitar.id)?.quantity ?? 26 | 0; 27 | return ( 28 |
32 | {guitar.name} 37 |
38 |
39 |

40 | {guitar.name} 41 |

42 | 43 | ${guitar.price} 44 | 45 |
46 |

{guitar.shortDescription}

47 |
48 | In Stock: 49 | 0 ? "text-green-400" : "text-red-400" 52 | }`} 53 | > 54 | {stock} units 55 | 56 |
57 |
58 |
59 | ); 60 | })} 61 |
62 |
63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /apps/admin/src/routes/orders.tsx: -------------------------------------------------------------------------------- 1 | import { createFileRoute } from "@tanstack/react-router"; 2 | import { 3 | flexRender, 4 | getCoreRowModel, 5 | createColumnHelper, 6 | useReactTable, 7 | } from "@tanstack/react-table"; 8 | import { fetchOrders, fetchGuitars } from "../utils/apis"; 9 | import type { Guitar, Order } from "../utils/apis"; 10 | const columnHelper = createColumnHelper(); 11 | 12 | const columns = [ 13 | columnHelper.accessor("customerName", { 14 | header: "Customer Name", 15 | cell: (info) => info.getValue(), 16 | filterFn: "includesString", 17 | }), 18 | columnHelper.accessor("items", { 19 | header: "Items", 20 | cell: (info) => { 21 | const items = info.getValue(); 22 | const guitars = 23 | (info.table.options.meta as { guitars: Guitar[] } | undefined) 24 | ?.guitars || []; 25 | return ( 26 |
27 | {items.map((item) => { 28 | const guitar = guitars.find((g) => g.id === item.guitarId); 29 | if (!guitar) return null; 30 | return ( 31 |
32 | {guitar.name} 37 |
38 |
{guitar.name}
39 |
40 | Qty: {item.quantity} 41 |
42 |
43 |
44 | ); 45 | })} 46 |
47 | ); 48 | }, 49 | }), 50 | columnHelper.accessor("totalAmount", { 51 | header: "Total Amount", 52 | cell: (info) => 53 | info.getValue().toLocaleString("en-US", { 54 | style: "currency", 55 | currency: "USD", 56 | }), 57 | }), 58 | columnHelper.accessor("orderDate", { 59 | header: "Order Date", 60 | cell: (info) => new Date(info.getValue()).toLocaleDateString(), 61 | }), 62 | ]; 63 | 64 | export const Route = createFileRoute("/orders")({ 65 | component: TableDemo, 66 | loader: async () => { 67 | const orders = await fetchOrders(); 68 | const guitars = await fetchGuitars(); 69 | return { orders, guitars }; 70 | }, 71 | }); 72 | 73 | function TableDemo() { 74 | const { orders, guitars } = Route.useLoaderData(); 75 | 76 | const table = useReactTable({ 77 | data: orders, 78 | columns, 79 | getCoreRowModel: getCoreRowModel(), 80 | meta: { 81 | guitars, 82 | orders, 83 | }, 84 | }); 85 | 86 | return ( 87 |
88 |
89 |
90 | 91 | 92 | {table.getHeaderGroups().map((headerGroup) => ( 93 | 94 | {headerGroup.headers.map((header) => ( 95 | 105 | ))} 106 | 107 | ))} 108 | 109 | 110 | {table.getRowModel().rows.map((row) => { 111 | return ( 112 | 116 | {row.getVisibleCells().map((cell) => { 117 | return ( 118 | 124 | ); 125 | })} 126 | 127 | ); 128 | })} 129 | 130 |
100 | {flexRender( 101 | header.column.columnDef.header, 102 | header.getContext() 103 | )} 104 |
119 | {flexRender( 120 | cell.column.columnDef.cell, 121 | cell.getContext() 122 | )} 123 |
131 |
132 |
133 |
134 | 141 | 148 | 155 | 162 | 163 |
Page
164 | 165 | {table.getState().pagination.pageIndex + 1} of{" "} 166 | {table.getPageCount()} 167 | 168 |
169 | 170 | | Go to page: 171 | { 175 | const page = e.target.value ? Number(e.target.value) - 1 : 0; 176 | table.setPageIndex(page); 177 | }} 178 | className="w-16 px-2 py-1 bg-gray-800 rounded-md border border-gray-700 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none" 179 | /> 180 | 181 | 194 |
195 |
196 | ); 197 | } 198 | -------------------------------------------------------------------------------- /apps/admin/src/ssr.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createStartHandler, 3 | defaultStreamHandler, 4 | } from '@tanstack/react-start/server' 5 | import { getRouterManifest } from '@tanstack/react-start/router-manifest' 6 | 7 | import { createRouter } from './router' 8 | 9 | export default createStartHandler({ 10 | createRouter, 11 | getRouterManifest, 12 | })(defaultStreamHandler) 13 | -------------------------------------------------------------------------------- /apps/admin/src/styles.css: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss'; 2 | 3 | @plugin "tailwindcss-animate"; 4 | 5 | @custom-variant dark (&:is(.dark *)); 6 | 7 | body { 8 | @apply m-0; 9 | font-family: 10 | -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 11 | 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | code { 17 | font-family: 18 | source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; 19 | } 20 | 21 | :root { 22 | --background: oklch(1 0 0); 23 | --foreground: oklch(0.141 0.005 285.823); 24 | --card: oklch(1 0 0); 25 | --card-foreground: oklch(0.141 0.005 285.823); 26 | --popover: oklch(1 0 0); 27 | --popover-foreground: oklch(0.141 0.005 285.823); 28 | --primary: oklch(0.21 0.006 285.885); 29 | --primary-foreground: oklch(0.985 0 0); 30 | --secondary: oklch(0.967 0.001 286.375); 31 | --secondary-foreground: oklch(0.21 0.006 285.885); 32 | --muted: oklch(0.967 0.001 286.375); 33 | --muted-foreground: oklch(0.552 0.016 285.938); 34 | --accent: oklch(0.967 0.001 286.375); 35 | --accent-foreground: oklch(0.21 0.006 285.885); 36 | --destructive: oklch(0.577 0.245 27.325); 37 | --destructive-foreground: oklch(0.577 0.245 27.325); 38 | --border: oklch(0.92 0.004 286.32); 39 | --input: oklch(0.92 0.004 286.32); 40 | --ring: oklch(0.871 0.006 286.286); 41 | --chart-1: oklch(0.646 0.222 41.116); 42 | --chart-2: oklch(0.6 0.118 184.704); 43 | --chart-3: oklch(0.398 0.07 227.392); 44 | --chart-4: oklch(0.828 0.189 84.429); 45 | --chart-5: oklch(0.769 0.188 70.08); 46 | --radius: 0.625rem; 47 | --sidebar: oklch(0.985 0 0); 48 | --sidebar-foreground: oklch(0.141 0.005 285.823); 49 | --sidebar-primary: oklch(0.21 0.006 285.885); 50 | --sidebar-primary-foreground: oklch(0.985 0 0); 51 | --sidebar-accent: oklch(0.967 0.001 286.375); 52 | --sidebar-accent-foreground: oklch(0.21 0.006 285.885); 53 | --sidebar-border: oklch(0.92 0.004 286.32); 54 | --sidebar-ring: oklch(0.871 0.006 286.286); 55 | } 56 | 57 | .dark { 58 | --background: oklch(0.141 0.005 285.823); 59 | --foreground: oklch(0.985 0 0); 60 | --card: oklch(0.141 0.005 285.823); 61 | --card-foreground: oklch(0.985 0 0); 62 | --popover: oklch(0.141 0.005 285.823); 63 | --popover-foreground: oklch(0.985 0 0); 64 | --primary: oklch(0.985 0 0); 65 | --primary-foreground: oklch(0.21 0.006 285.885); 66 | --secondary: oklch(0.274 0.006 286.033); 67 | --secondary-foreground: oklch(0.985 0 0); 68 | --muted: oklch(0.274 0.006 286.033); 69 | --muted-foreground: oklch(0.705 0.015 286.067); 70 | --accent: oklch(0.274 0.006 286.033); 71 | --accent-foreground: oklch(0.985 0 0); 72 | --destructive: oklch(0.396 0.141 25.723); 73 | --destructive-foreground: oklch(0.637 0.237 25.331); 74 | --border: oklch(0.274 0.006 286.033); 75 | --input: oklch(0.274 0.006 286.033); 76 | --ring: oklch(0.442 0.017 285.786); 77 | --chart-1: oklch(0.488 0.243 264.376); 78 | --chart-2: oklch(0.696 0.17 162.48); 79 | --chart-3: oklch(0.769 0.188 70.08); 80 | --chart-4: oklch(0.627 0.265 303.9); 81 | --chart-5: oklch(0.645 0.246 16.439); 82 | --sidebar: oklch(0.21 0.006 285.885); 83 | --sidebar-foreground: oklch(0.985 0 0); 84 | --sidebar-primary: oklch(0.488 0.243 264.376); 85 | --sidebar-primary-foreground: oklch(0.985 0 0); 86 | --sidebar-accent: oklch(0.274 0.006 286.033); 87 | --sidebar-accent-foreground: oklch(0.985 0 0); 88 | --sidebar-border: oklch(0.274 0.006 286.033); 89 | --sidebar-ring: oklch(0.442 0.017 285.786); 90 | } 91 | 92 | @theme inline { 93 | --color-background: var(--background); 94 | --color-foreground: var(--foreground); 95 | --color-card: var(--card); 96 | --color-card-foreground: var(--card-foreground); 97 | --color-popover: var(--popover); 98 | --color-popover-foreground: var(--popover-foreground); 99 | --color-primary: var(--primary); 100 | --color-primary-foreground: var(--primary-foreground); 101 | --color-secondary: var(--secondary); 102 | --color-secondary-foreground: var(--secondary-foreground); 103 | --color-muted: var(--muted); 104 | --color-muted-foreground: var(--muted-foreground); 105 | --color-accent: var(--accent); 106 | --color-accent-foreground: var(--accent-foreground); 107 | --color-destructive: var(--destructive); 108 | --color-destructive-foreground: var(--destructive-foreground); 109 | --color-border: var(--border); 110 | --color-input: var(--input); 111 | --color-ring: var(--ring); 112 | --color-chart-1: var(--chart-1); 113 | --color-chart-2: var(--chart-2); 114 | --color-chart-3: var(--chart-3); 115 | --color-chart-4: var(--chart-4); 116 | --color-chart-5: var(--chart-5); 117 | --radius-sm: calc(var(--radius) - 4px); 118 | --radius-md: calc(var(--radius) - 2px); 119 | --radius-lg: var(--radius); 120 | --radius-xl: calc(var(--radius) + 4px); 121 | --color-sidebar: var(--sidebar); 122 | --color-sidebar-foreground: var(--sidebar-foreground); 123 | --color-sidebar-primary: var(--sidebar-primary); 124 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); 125 | --color-sidebar-accent: var(--sidebar-accent); 126 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); 127 | --color-sidebar-border: var(--sidebar-border); 128 | --color-sidebar-ring: var(--sidebar-ring); 129 | } 130 | 131 | @layer base { 132 | * { 133 | @apply border-border outline-ring/50; 134 | } 135 | body { 136 | @apply bg-background text-foreground; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /apps/admin/src/utils/apis.ts: -------------------------------------------------------------------------------- 1 | export interface Guitar { 2 | id: number; 3 | name: string; 4 | image: string; 5 | description: string; 6 | shortDescription: string; 7 | price: number; 8 | } 9 | 10 | export interface InventoryItem { 11 | guitarId: number; 12 | quantity: number; 13 | } 14 | 15 | export interface Order { 16 | id: number; 17 | customerName: string; 18 | items: Array<{ 19 | guitarId: number; 20 | quantity: number; 21 | }>; 22 | totalAmount: number; 23 | orderDate: string; 24 | } 25 | 26 | export const fetchGuitars = async () => { 27 | const response = await fetch("http://localhost:8082/products"); 28 | if (!response.ok) { 29 | throw new Error("Network response was not ok"); 30 | } 31 | return response.json() as unknown as Guitar[]; 32 | }; 33 | 34 | export const fetchInventory = async () => { 35 | const response = await fetch("http://localhost:8080/inventory"); 36 | if (!response.ok) { 37 | throw new Error("Network response was not ok"); 38 | } 39 | return response.json() as unknown as InventoryItem[]; 40 | }; 41 | 42 | export const fetchOrders = async () => { 43 | const response = await fetch("http://localhost:8080/orders"); 44 | if (!response.ok) { 45 | throw new Error("Network response was not ok"); 46 | } 47 | return response.json() as unknown as Order[]; 48 | }; 49 | -------------------------------------------------------------------------------- /apps/admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["**/*.ts", "**/*.tsx"], 3 | "compilerOptions": { 4 | "target": "ES2022", 5 | "jsx": "react-jsx", 6 | "module": "ESNext", 7 | "lib": ["ES2022", "DOM", "DOM.Iterable"], 8 | "types": ["vite/client"], 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "verbatimModuleSyntax": true, 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "skipLibCheck": true, 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "noUncheckedSideEffectImports": true, 23 | "baseUrl": ".", 24 | "paths": { 25 | "@/*": ["./src/*"], 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /apps/frontend/.cursorrules: -------------------------------------------------------------------------------- 1 | # shadcn instructions 2 | 3 | Use the latest version of Shadcn to install new components, like this command to add a button component: 4 | 5 | ```bash 6 | pnpx shadcn@latest add button 7 | ``` 8 | -------------------------------------------------------------------------------- /apps/frontend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | .output 7 | .vinxi 8 | -------------------------------------------------------------------------------- /apps/frontend/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.watcherExclude": { 3 | "**/routeTree.gen.ts": true 4 | }, 5 | "search.exclude": { 6 | "**/routeTree.gen.ts": true 7 | }, 8 | "files.readonlyInclude": { 9 | "**/routeTree.gen.ts": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /apps/frontend/README.md: -------------------------------------------------------------------------------- 1 | Welcome to your new TanStack app! 2 | 3 | # Getting Started 4 | 5 | To run this application: 6 | 7 | ```bash 8 | pnpm install 9 | pnpm start 10 | ``` 11 | 12 | # Building For Production 13 | 14 | To build this application for production: 15 | 16 | ```bash 17 | pnpm build 18 | ``` 19 | 20 | ## Testing 21 | 22 | This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with: 23 | 24 | ```bash 25 | pnpm test 26 | ``` 27 | 28 | ## Styling 29 | 30 | This project uses [Tailwind CSS](https://tailwindcss.com/) for styling. 31 | 32 | 33 | 34 | 35 | ## Shadcn 36 | 37 | Add components using the latest version of [Shadcn](https://ui.shadcn.com/). 38 | 39 | ```bash 40 | pnpx shadcn@latest add button 41 | ``` 42 | 43 | 44 | # TanStack Chat Application 45 | 46 | Am example chat application built with TanStack Start, TanStack Store, and Claude AI. 47 | 48 | ## .env Updates 49 | 50 | ```env 51 | ANTHROPIC_API_KEY=your_anthropic_api_key 52 | ``` 53 | 54 | ## ✨ Features 55 | 56 | ### AI Capabilities 57 | - 🤖 Powered by Claude 3.5 Sonnet 58 | - 📝 Rich markdown formatting with syntax highlighting 59 | - 🎯 Customizable system prompts for tailored AI behavior 60 | - 🔄 Real-time message updates and streaming responses (coming soon) 61 | 62 | ### User Experience 63 | - 🎨 Modern UI with Tailwind CSS and Lucide icons 64 | - 🔍 Conversation management and history 65 | - 🔐 Secure API key management 66 | - 📋 Markdown rendering with code highlighting 67 | 68 | ### Technical Features 69 | - 📦 Centralized state management with TanStack Store 70 | - 🔌 Extensible architecture for multiple AI providers 71 | - 🛠️ TypeScript for type safety 72 | 73 | ## Architecture 74 | 75 | ### Tech Stack 76 | - **Frontend Framework**: TanStack Start 77 | - **Routing**: TanStack Router 78 | - **State Management**: TanStack Store 79 | - **Styling**: Tailwind CSS 80 | - **AI Integration**: Anthropic's Claude API 81 | 82 | 83 | ## Routing 84 | This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a file based router. Which means that the routes are managed as files in `src/routes`. 85 | 86 | ### Adding A Route 87 | 88 | To add a new route to your application just add another a new file in the `./src/routes` directory. 89 | 90 | TanStack will automatically generate the content of the route file for you. 91 | 92 | Now that you have two routes you can use a `Link` component to navigate between them. 93 | 94 | ### Adding Links 95 | 96 | To use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/react-router`. 97 | 98 | ```tsx 99 | import { Link } from "@tanstack/react-router"; 100 | ``` 101 | 102 | Then anywhere in your JSX you can use it like so: 103 | 104 | ```tsx 105 | About 106 | ``` 107 | 108 | This will create a link that will navigate to the `/about` route. 109 | 110 | More information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent). 111 | 112 | ### Using A Layout 113 | 114 | In the File Based Routing setup the layout is located in `src/routes/__root.tsx`. Anything you add to the root route will appear in all the routes. The route content will appear in the JSX where you use the `` component. 115 | 116 | Here is an example layout that includes a header: 117 | 118 | ```tsx 119 | import { Outlet, createRootRoute } from '@tanstack/react-router' 120 | import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' 121 | 122 | import { Link } from "@tanstack/react-router"; 123 | 124 | export const Route = createRootRoute({ 125 | component: () => ( 126 | <> 127 |
128 | 132 |
133 | 134 | 135 | 136 | ), 137 | }) 138 | ``` 139 | 140 | The `` component is not required so you can remove it if you don't want it in your layout. 141 | 142 | More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts). 143 | 144 | 145 | ## Data Fetching 146 | 147 | There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered. 148 | 149 | For example: 150 | 151 | ```tsx 152 | const peopleRoute = createRoute({ 153 | getParentRoute: () => rootRoute, 154 | path: "/people", 155 | loader: async () => { 156 | const response = await fetch("https://swapi.dev/api/people"); 157 | return response.json() as Promise<{ 158 | results: { 159 | name: string; 160 | }[]; 161 | }>; 162 | }, 163 | component: () => { 164 | const data = peopleRoute.useLoaderData(); 165 | return ( 166 |
    167 | {data.results.map((person) => ( 168 |
  • {person.name}
  • 169 | ))} 170 |
171 | ); 172 | }, 173 | }); 174 | ``` 175 | 176 | Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters). 177 | 178 | ### React-Query 179 | 180 | React-Query is an excellent addition or alternative to route loading and integrating it into you application is a breeze. 181 | 182 | First add your dependencies: 183 | 184 | ```bash 185 | pnpm add @tanstack/react-query @tanstack/react-query-devtools 186 | ``` 187 | 188 | Next we'll need to create a query client and provider. We recommend putting those in `main.tsx`. 189 | 190 | ```tsx 191 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 192 | 193 | // ... 194 | 195 | const queryClient = new QueryClient(); 196 | 197 | // ... 198 | 199 | if (!rootElement.innerHTML) { 200 | const root = ReactDOM.createRoot(rootElement); 201 | 202 | root.render( 203 | 204 | 205 | 206 | ); 207 | } 208 | ``` 209 | 210 | You can also add TanStack Query Devtools to the root route (optional). 211 | 212 | ```tsx 213 | import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; 214 | 215 | const rootRoute = createRootRoute({ 216 | component: () => ( 217 | <> 218 | 219 | 220 | 221 | 222 | ), 223 | }); 224 | ``` 225 | 226 | Now you can use `useQuery` to fetch your data. 227 | 228 | ```tsx 229 | import { useQuery } from "@tanstack/react-query"; 230 | 231 | import "./App.css"; 232 | 233 | function App() { 234 | const { data } = useQuery({ 235 | queryKey: ["people"], 236 | queryFn: () => 237 | fetch("https://swapi.dev/api/people") 238 | .then((res) => res.json()) 239 | .then((data) => data.results as { name: string }[]), 240 | initialData: [], 241 | }); 242 | 243 | return ( 244 |
245 |
    246 | {data.map((person) => ( 247 |
  • {person.name}
  • 248 | ))} 249 |
250 |
251 | ); 252 | } 253 | 254 | export default App; 255 | ``` 256 | 257 | You can find out everything you need to know on how to use React-Query in the [React-Query documentation](https://tanstack.com/query/latest/docs/framework/react/overview). 258 | 259 | ## State Management 260 | 261 | Another common requirement for React applications is state management. There are many options for state management in React. TanStack Store provides a great starting point for your project. 262 | 263 | First you need to add TanStack Store as a dependency: 264 | 265 | ```bash 266 | pnpm add @tanstack/store 267 | ``` 268 | 269 | Now let's create a simple counter in the `src/App.tsx` file as a demonstration. 270 | 271 | ```tsx 272 | import { useStore } from "@tanstack/react-store"; 273 | import { Store } from "@tanstack/store"; 274 | import "./App.css"; 275 | 276 | const countStore = new Store(0); 277 | 278 | function App() { 279 | const count = useStore(countStore); 280 | return ( 281 |
282 | 285 |
286 | ); 287 | } 288 | 289 | export default App; 290 | ``` 291 | 292 | One of the many nice features of TanStack Store is the ability to derive state from other state. That derived state will update when the base state updates. 293 | 294 | Let's check this out by doubling the count using derived state. 295 | 296 | ```tsx 297 | import { useStore } from "@tanstack/react-store"; 298 | import { Store, Derived } from "@tanstack/store"; 299 | import "./App.css"; 300 | 301 | const countStore = new Store(0); 302 | 303 | const doubledStore = new Derived({ 304 | fn: () => countStore.state * 2, 305 | deps: [countStore], 306 | }); 307 | doubledStore.mount(); 308 | 309 | function App() { 310 | const count = useStore(countStore); 311 | const doubledCount = useStore(doubledStore); 312 | 313 | return ( 314 |
315 | 318 |
Doubled - {doubledCount}
319 |
320 | ); 321 | } 322 | 323 | export default App; 324 | ``` 325 | 326 | We use the `Derived` class to create a new store that is derived from another store. The `Derived` class has a `mount` method that will start the derived store updating. 327 | 328 | Once we've created the derived store we can use it in the `App` component just like we would any other store using the `useStore` hook. 329 | 330 | You can find out everything you need to know on how to use TanStack Store in the [TanStack Store documentation](https://tanstack.com/store/latest). 331 | 332 | # Demo files 333 | 334 | Files prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed. 335 | 336 | # Learn More 337 | 338 | You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com). 339 | -------------------------------------------------------------------------------- /apps/frontend/app.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@tanstack/react-start/config' 2 | import viteTsConfigPaths from 'vite-tsconfig-paths' 3 | import tailwindcss from '@tailwindcss/vite' 4 | 5 | export default defineConfig({ 6 | tsr: { 7 | appDirectory: 'src', 8 | }, 9 | vite: { 10 | plugins: [ 11 | // this is the plugin that enables path aliases 12 | viteTsConfigPaths({ 13 | projects: ['./tsconfig.json'], 14 | }), 15 | tailwindcss(), 16 | ], 17 | }, 18 | }) 19 | -------------------------------------------------------------------------------- /apps/frontend/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/styles.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /apps/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "type": "module", 5 | "scripts": { 6 | "start": "vinxi start", 7 | "build": "vinxi build", 8 | "serve": "vite preview", 9 | "test": "vitest run", 10 | "dev": "PORT=3000 vinxi dev" 11 | }, 12 | "dependencies": { 13 | "@ai-sdk/anthropic": "^1.1.17", 14 | "@ai-sdk/react": "^1.1.23", 15 | "@faker-js/faker": "^9.6.0", 16 | "@modelcontextprotocol/sdk": "^1.7.0", 17 | "@tailwindcss/postcss": "^4.0.7", 18 | "@tailwindcss/vite": "^4.0.6", 19 | "@tanstack/match-sorter-utils": "^8.19.4", 20 | "@tanstack/react-query": "^5.66.5", 21 | "@tanstack/react-query-devtools": "^5.66.5", 22 | "@tanstack/react-router": "^1.114.3", 23 | "@tanstack/react-router-devtools": "^1.114.3", 24 | "@tanstack/react-router-with-query": "^1.114.3", 25 | "@tanstack/react-start": "^1.114.3", 26 | "@tanstack/react-store": "^0.7.0", 27 | "@tanstack/react-table": "^8.21.2", 28 | "@tanstack/router-plugin": "^1.114.3", 29 | "@tanstack/store": "^0.7.0", 30 | "ai": "^4.1.61", 31 | "class-variance-authority": "^0.7.1", 32 | "clsx": "^2.1.1", 33 | "date-fns": "^4.1.0", 34 | "highlight.js": "^11.11.1", 35 | "lucide-react": "^0.475.0", 36 | "postcss": "^8.5.2", 37 | "react": "^19.0.0", 38 | "react-dom": "^19.0.0", 39 | "react-markdown": "^9.0.1", 40 | "rehype-highlight": "^7.0.0", 41 | "rehype-raw": "^7.0.0", 42 | "rehype-sanitize": "^6.0.0", 43 | "remark-gfm": "^4.0.1", 44 | "tailwind-merge": "^3.0.2", 45 | "tailwindcss": "^4.0.6", 46 | "tailwindcss-animate": "^1.0.7", 47 | "vinxi": "^0.5.3", 48 | "vite-tsconfig-paths": "^5.1.4", 49 | "zod": "^3.24.2" 50 | }, 51 | "devDependencies": { 52 | "@testing-library/dom": "^10.4.0", 53 | "@testing-library/react": "^16.2.0", 54 | "@types/node": "^22.13.10", 55 | "@types/react": "^19.0.8", 56 | "@types/react-dom": "^19.0.3", 57 | "@vitejs/plugin-react": "^4.3.4", 58 | "jsdom": "^26.0.0", 59 | "typescript": "^5.7.2", 60 | "vite": "^6.1.0", 61 | "vitest": "^3.0.5", 62 | "web-vitals": "^4.2.4" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /apps/frontend/postcss.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | "@tailwindcss/postcss": {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /apps/frontend/public/example-guitar-dune.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/mcp-client-and-server/d76c6cf352e03e31cba8397bf28f33103e7d7c9a/apps/frontend/public/example-guitar-dune.jpg -------------------------------------------------------------------------------- /apps/frontend/public/example-guitar-motherboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/mcp-client-and-server/d76c6cf352e03e31cba8397bf28f33103e7d7c9a/apps/frontend/public/example-guitar-motherboard.jpg -------------------------------------------------------------------------------- /apps/frontend/public/example-guitar-racing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/mcp-client-and-server/d76c6cf352e03e31cba8397bf28f33103e7d7c9a/apps/frontend/public/example-guitar-racing.jpg -------------------------------------------------------------------------------- /apps/frontend/public/example-guitar-steamer-trunk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/mcp-client-and-server/d76c6cf352e03e31cba8397bf28f33103e7d7c9a/apps/frontend/public/example-guitar-steamer-trunk.jpg -------------------------------------------------------------------------------- /apps/frontend/public/example-guitar-steampunk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/mcp-client-and-server/d76c6cf352e03e31cba8397bf28f33103e7d7c9a/apps/frontend/public/example-guitar-steampunk.jpg -------------------------------------------------------------------------------- /apps/frontend/public/example-guitar-underwater.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/mcp-client-and-server/d76c6cf352e03e31cba8397bf28f33103e7d7c9a/apps/frontend/public/example-guitar-underwater.jpg -------------------------------------------------------------------------------- /apps/frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/mcp-client-and-server/d76c6cf352e03e31cba8397bf28f33103e7d7c9a/apps/frontend/public/favicon.ico -------------------------------------------------------------------------------- /apps/frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/mcp-client-and-server/d76c6cf352e03e31cba8397bf28f33103e7d7c9a/apps/frontend/public/logo192.png -------------------------------------------------------------------------------- /apps/frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jherr/mcp-client-and-server/d76c6cf352e03e31cba8397bf28f33103e7d7c9a/apps/frontend/public/logo512.png -------------------------------------------------------------------------------- /apps/frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "TanStack App", 3 | "name": "Create TanStack App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /apps/frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /apps/frontend/src/api.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createStartAPIHandler, 3 | defaultAPIFileRouteHandler, 4 | } from "@tanstack/react-start/api"; 5 | 6 | export default createStartAPIHandler(defaultAPIFileRouteHandler); 7 | -------------------------------------------------------------------------------- /apps/frontend/src/client.tsx: -------------------------------------------------------------------------------- 1 | import { hydrateRoot } from 'react-dom/client' 2 | import { StartClient } from '@tanstack/react-start' 3 | 4 | import { createRouter } from './router' 5 | 6 | const router = createRouter() 7 | 8 | hydrateRoot(document, ) 9 | -------------------------------------------------------------------------------- /apps/frontend/src/components/AIAssistant.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | import { useStore } from "@tanstack/react-store"; 3 | import { Send, X } from "lucide-react"; 4 | import ReactMarkdown from "react-markdown"; 5 | import rehypeRaw from "rehype-raw"; 6 | import rehypeSanitize from "rehype-sanitize"; 7 | import rehypeHighlight from "rehype-highlight"; 8 | import remarkGfm from "remark-gfm"; 9 | import { useChat } from "@ai-sdk/react"; 10 | import { genAIResponse } from "../utils/ai"; 11 | 12 | import { showAIAssistant } from "../store/assistant"; 13 | 14 | import GuitarRecommendation from "./GuitarRecommendation"; 15 | 16 | import type { UIMessage } from "ai"; 17 | 18 | function Messages({ messages }: { messages: Array }) { 19 | const messagesContainerRef = useRef(null); 20 | 21 | useEffect(() => { 22 | if (messagesContainerRef.current) { 23 | messagesContainerRef.current.scrollTop = 24 | messagesContainerRef.current.scrollHeight; 25 | } 26 | }, [messages]); 27 | 28 | if (!messages.length) { 29 | return ( 30 |
31 | Ask me anything! I'm here to help. 32 |
33 | ); 34 | } 35 | 36 | return ( 37 |
38 | {messages.map(({ id, role, content, parts }) => ( 39 |
47 | {content.length > 0 && ( 48 |
49 | {role === "assistant" ? ( 50 |
51 | AI 52 |
53 | ) : ( 54 |
55 | Y 56 |
57 | )} 58 |
59 | 68 | {content} 69 | 70 |
71 |
72 | )} 73 | {parts 74 | .filter((part) => part.type === "tool-invocation") 75 | .filter( 76 | (part) => part.toolInvocation.toolName === "recommendGuitar" 77 | ) 78 | .map((toolCall) => ( 79 |
83 | 84 |
85 | ))} 86 |
87 | ))} 88 |
89 | ); 90 | } 91 | 92 | export default function AIAssistant() { 93 | const isOpen = useStore(showAIAssistant); 94 | const { messages, input, handleInputChange, handleSubmit } = useChat({ 95 | initialMessages: [], 96 | fetch: (_url, options) => { 97 | const { messages } = JSON.parse(options!.body! as string); 98 | return genAIResponse({ 99 | data: { 100 | messages, 101 | }, 102 | }); 103 | }, 104 | onToolCall: (call) => { 105 | if (call.toolCall.toolName === "recommendGuitar") { 106 | return "Handled by the UI"; 107 | } 108 | }, 109 | }); 110 | 111 | return ( 112 |
113 | 122 | 123 | {isOpen && ( 124 |
125 |
126 |

AI Assistant

127 | 133 |
134 | 135 | 136 | 137 |
138 |
139 |
140 |