├── .DS_Store ├── .gitignore ├── README.md ├── agent ├── README.md ├── bun.lockb ├── index.ts ├── package.json ├── schema.sql ├── seed.sql └── tsconfig.json └── react-ui ├── bun.lockb ├── components.json ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public └── placeholder.svg ├── src ├── App.css ├── App.tsx ├── components │ ├── ChatMessage.tsx │ ├── ChatWidget.tsx │ ├── FeedbackForm.tsx │ ├── ProductCard.tsx │ ├── StoreFooter.tsx │ ├── StoreHeader.tsx │ └── ui │ │ ├── accordion.tsx │ │ ├── alert-dialog.tsx │ │ ├── alert.tsx │ │ ├── aspect-ratio.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── breadcrumb.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── card.tsx │ │ ├── carousel.tsx │ │ ├── chart.tsx │ │ ├── checkbox.tsx │ │ ├── collapsible.tsx │ │ ├── command.tsx │ │ ├── context-menu.tsx │ │ ├── dialog.tsx │ │ ├── drawer.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── hover-card.tsx │ │ ├── input-otp.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── menubar.tsx │ │ ├── navigation-menu.tsx │ │ ├── pagination.tsx │ │ ├── popover.tsx │ │ ├── progress.tsx │ │ ├── radio-group.tsx │ │ ├── resizable.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── sidebar.tsx │ │ ├── skeleton.tsx │ │ ├── slider.tsx │ │ ├── sonner.tsx │ │ ├── switch.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── toast.tsx │ │ ├── toaster.tsx │ │ ├── toggle-group.tsx │ │ ├── toggle.tsx │ │ ├── tooltip.tsx │ │ └── use-toast.ts ├── hooks │ ├── use-mobile.tsx │ └── use-toast.ts ├── index.css ├── lib │ └── utils.ts ├── main.tsx ├── pages │ └── Index.tsx └── vite-env.d.ts ├── tailwind.config.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersdigest/bee-agent/2d4575eaabddbd51c73facc352a98705e8a360e5/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore 2 | 3 | # Logs 4 | 5 | logs 6 | _.log 7 | npm-debug.log_ 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Caches 14 | 15 | .cache 16 | 17 | # Diagnostic reports (https://nodejs.org/api/report.html) 18 | 19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 20 | 21 | # Runtime data 22 | 23 | pids 24 | _.pid 25 | _.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | 34 | coverage 35 | *.lcov 36 | 37 | # nyc test coverage 38 | 39 | .nyc_output 40 | 41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 42 | 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | 47 | bower_components 48 | 49 | # node-waf configuration 50 | 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | 55 | build/Release 56 | 57 | # Dependency directories 58 | 59 | node_modules/ 60 | jspm_packages/ 61 | 62 | # Snowpack dependency directory (https://snowpack.dev/) 63 | 64 | web_modules/ 65 | 66 | # TypeScript cache 67 | 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | 72 | .npm 73 | 74 | # Optional eslint cache 75 | 76 | .eslintcache 77 | 78 | # Optional stylelint cache 79 | 80 | .stylelintcache 81 | 82 | # Microbundle cache 83 | 84 | .rpt2_cache/ 85 | .rts2_cache_cjs/ 86 | .rts2_cache_es/ 87 | .rts2_cache_umd/ 88 | 89 | # Optional REPL history 90 | 91 | .node_repl_history 92 | 93 | # Output of 'npm pack' 94 | 95 | *.tgz 96 | 97 | # Yarn Integrity file 98 | 99 | .yarn-integrity 100 | 101 | # dotenv environment variable files 102 | 103 | .env 104 | .env.development.local 105 | .env.test.local 106 | .env.production.local 107 | .env.local 108 | 109 | # parcel-bundler cache (https://parceljs.org/) 110 | 111 | .parcel-cache 112 | 113 | # Next.js build output 114 | 115 | .next 116 | out 117 | 118 | # Nuxt.js build / generate output 119 | 120 | .nuxt 121 | dist 122 | 123 | # Gatsby files 124 | 125 | # Comment in the public line in if your project uses Gatsby and not Next.js 126 | 127 | # https://nextjs.org/blog/next-9-1#public-directory-support 128 | 129 | # public 130 | 131 | # vuepress build output 132 | 133 | .vuepress/dist 134 | 135 | # vuepress v2.x temp and cache directory 136 | 137 | .temp 138 | 139 | # Docusaurus cache and generated files 140 | 141 | .docusaurus 142 | 143 | # Serverless directories 144 | 145 | .serverless/ 146 | 147 | # FuseBox cache 148 | 149 | .fusebox/ 150 | 151 | # DynamoDB Local files 152 | 153 | .dynamodb/ 154 | 155 | # TernJS port file 156 | 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | 161 | .vscode-test 162 | 163 | # yarn v2 164 | 165 | .yarn/cache 166 | .yarn/unplugged 167 | .yarn/build-state.yml 168 | .yarn/install-state.gz 169 | .pnp.* 170 | 171 | # IntelliJ based IDEs 172 | .idea 173 | 174 | # Finder (MacOS) folder config 175 | .DS_Store 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Developers Digest Coffee 3 | 4 | Welcome to the Developers Digest Coffee project! This is a web application for a coffee shop that caters specifically to developers, offering carefully curated coffee from America's top tech hubs. 5 | 6 | ## Project Structure 7 | 8 | This project consists of two main parts: 9 | 10 | 1. Frontend (UI): Located in the `react-ui` directory 11 | 2. Backend (Agent): Located in the `agent` directory 12 | 13 | ## Frontend (UI) 14 | 15 | The frontend is built using: 16 | 17 | - Vite 18 | - React 19 | - TypeScript 20 | - shadcn-ui 21 | - Tailwind CSS 22 | 23 | ### Getting Started 24 | 25 | To run the frontend: 26 | 27 | 1. Navigate to the `ui` directory 28 | 2. Install dependencies: 29 | ``` 30 | npm install 31 | ``` 32 | 3. Start the development server: 33 | ``` 34 | npm run dev 35 | ``` 36 | 37 | The server will start on `http://localhost:8080`. 38 | 39 | ## Backend (Agent) 40 | 41 | The backend is built using: 42 | 43 | - Bun 44 | - TypeScript 45 | 46 | ### Getting Started 47 | 48 | To run the backend: 49 | 50 | 1. Navigate to the `agent` directory 51 | 2. Install dependencies: 52 | ``` 53 | bun install 54 | ``` 55 | 3. Start the server: 56 | ``` 57 | bun run index.ts 58 | ``` 59 | 60 | ## Features 61 | 62 | - Responsive design for various screen sizes 63 | - Product catalog showcasing different coffee blends 64 | - Information about the coffee sourcing and roasting process 65 | - Coffee Club subscription option 66 | - Chat widget for customer support 67 | 68 | ## Development 69 | 70 | This project uses ESLint for linting and Prettier for code formatting. Make sure to run these tools before committing your changes. 71 | 72 | ## Deployment 73 | 74 | For deployment options, consider using platforms like Netlify, Vercel, or AWS Amplify. These services offer easy integration with Git repositories and provide automatic builds and deployments. 75 | 76 | ## Contributing 77 | 78 | Contributions are welcome! Please feel free to submit a Pull Request. 79 | 80 | ## License 81 | 82 | MIT License 83 | 84 | Copyright (c) 2024 Developers Digest 85 | 86 | Permission is hereby granted, free of charge, to any person obtaining a copy 87 | of this software and associated documentation files (the "Software"), to deal 88 | in the Software without restriction, including without limitation the rights 89 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 90 | copies of the Software, and to permit persons to whom the Software is 91 | furnished to do so, subject to the following conditions: 92 | 93 | The above copyright notice and this permission notice shall be included in all 94 | copies or substantial portions of the Software. 95 | 96 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 97 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 98 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 99 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 100 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 101 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 102 | SOFTWARE. -------------------------------------------------------------------------------- /agent/README.md: -------------------------------------------------------------------------------- 1 | # bee-backend 2 | 3 | To install dependencies: 4 | 5 | ```bash 6 | bun install 7 | ``` 8 | 9 | To run: 10 | 11 | ```bash 12 | bun run index.ts 13 | ``` 14 | 15 | This project was created using `bun init` in bun v1.1.20. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. 16 | -------------------------------------------------------------------------------- /agent/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersdigest/bee-agent/2d4575eaabddbd51c73facc352a98705e8a360e5/agent/bun.lockb -------------------------------------------------------------------------------- /agent/index.ts: -------------------------------------------------------------------------------- 1 | // 1. Import required dependencies 2 | import express, { type Request, type Response } from 'express'; 3 | import { BeeAgent } from "bee-agent-framework/agents/bee/agent"; 4 | import { GroqChatLLM } from "bee-agent-framework/adapters/groq/chat"; 5 | import { DynamicTool, StringToolOutput } from "bee-agent-framework/tools/base"; 6 | import { FrameworkError } from "bee-agent-framework/errors"; 7 | import { UnconstrainedMemory } from "bee-agent-framework/memory/unconstrainedMemory"; 8 | import { z } from "zod"; 9 | import cors from 'cors'; 10 | import pool from './db'; 11 | 12 | // 2. Initialize Express app with middleware 13 | const app = express(); 14 | app.use(express.json()); 15 | app.use(cors()); 16 | 17 | // 3. Configure LLM with Groq model 18 | const llm = new GroqChatLLM({ 19 | modelId: "llama-3.3-70b-specdec", 20 | parameters: { 21 | temperature: 0, // Set to 0 for consistent, deterministic responses 22 | }, 23 | }); 24 | 25 | // 4. Define brewing guide tool schema and configuration 26 | const getBrewingGuideTool = new DynamicTool({ 27 | name: "GetBrewingGuide", 28 | description: "Provides customized brewing instructions based on method and preferences", 29 | inputSchema: z.object({ 30 | method: z.enum(["pourover", "french-press", "espresso", "cold-brew", "aeropress", "moka-pot"]), 31 | strength: z.enum(["light", "medium", "strong"]), 32 | servings: z.number().min(1).max(8) 33 | }), 34 | 35 | // 5. Implement brewing guide tool handler 36 | async handler(input) { 37 | try { 38 | // 5.1. Get brewing method details 39 | const methodResult = await pool.query( 40 | 'SELECT * FROM brewing_methods WHERE name = $1', 41 | [input.method] 42 | ); 43 | const method = methodResult.rows[0]; 44 | 45 | // 5.2. Get ratio for strength 46 | const ratioResult = await pool.query( 47 | 'SELECT ratio FROM brewing_ratios WHERE strength = $1', 48 | [input.strength] 49 | ); 50 | const ratio = ratioResult.rows[0].ratio; 51 | 52 | // 5.3. Calculate measurements 53 | const coffeeGrams = input.servings * 15; // Base 15g per serving 54 | const waterMl = coffeeGrams * ratio; 55 | 56 | return new StringToolOutput( 57 | `Recipe for ${input.servings} serving(s) of ${input.strength} ${input.method}:\n` + 58 | `Coffee: ${coffeeGrams}g\n` + 59 | `Water: ${waterMl}ml\n` + 60 | `Grind Size: ${method.grind_size}\n` + 61 | `Water Temperature: ${method.water_temp}\n` + 62 | `Total Brew Time: ${method.brew_time}\n` 63 | ); 64 | } catch (error) { 65 | console.error('Database error:', error); 66 | throw new Error('Failed to retrieve brewing guide information'); 67 | } 68 | }, 69 | }); 70 | 71 | // 6. Define shipping estimates tool schema 72 | const getShippingEstimateTool = new DynamicTool({ 73 | name: "GetShippingEstimate", 74 | description: "Provides shipping estimates and delivery information for different regions", 75 | inputSchema: z.object({ 76 | region: z.enum([ 77 | "USA-West", "USA-Central", "USA-East", 78 | "Canada", "Europe", "Asia", "Australia", 79 | "South-America", "Other" 80 | ]), 81 | method: z.enum(["standard", "express", "international"]).optional() 82 | }), 83 | 84 | // 7. Implement shipping estimates tool handler 85 | async handler(input) { 86 | try { 87 | // 7.1. Query shipping data 88 | const result = await pool.query( 89 | 'SELECT * FROM shipping_regions WHERE name = $1', 90 | [input.region] 91 | ); 92 | 93 | // 7.2. Process shipping details 94 | const regionData = result.rows[0]; 95 | const method = input.method || "standard"; 96 | const deliveryTime = method === "express" ? regionData.express_delivery : regionData.standard_delivery; 97 | 98 | return new StringToolOutput( 99 | `Shipping to ${input.region}:\n` + 100 | `Delivery Time (${method}): ${deliveryTime}\n` + 101 | `Shipping Cost: ${regionData.cost}\n` + 102 | `Additional Information: ${regionData.notes}\n\n` + 103 | `All orders include:\n` + 104 | `- Free tracking\n` + 105 | `- Freshly roasted guarantee\n` + 106 | `- Sustainable packaging\n` + 107 | `- 30-day satisfaction guarantee` 108 | ); 109 | } catch (error) { 110 | console.error('Database error:', error); 111 | throw new Error('Failed to retrieve shipping information'); 112 | } 113 | } 114 | }); 115 | 116 | // 8. Define coffee club membership tool 117 | const handleCoffeeClubTool = new DynamicTool({ 118 | name: "HandleCoffeeClub", 119 | description: "Handle coffee club membership queries and signups as a component in the UI. This only shows explicity the coffee club modal, and has nothing to do with contacting the company.", 120 | inputSchema: z.object({ 121 | action: z.string() 122 | }), 123 | 124 | // 9. Implement coffee club tool handler 125 | async handler(input) { 126 | try { 127 | // 9.1. Get coffee club benefits 128 | const result = await pool.query('SELECT benefit FROM coffee_club_benefits'); 129 | const benefits = result.rows.map(row => `- ${row.benefit}`).join('\n'); 130 | return new StringToolOutput( 131 | "I'll help you join our Coffee Club! " + 132 | "Our Coffee Club members enjoy:\n" + 133 | benefits + "\n\n" + 134 | "SHOW_COFFEE_CLUB_MODAL" // Special flag to show modal 135 | ); 136 | } catch (error) { 137 | console.error('Database error:', error); 138 | throw new Error('Failed to retrieve coffee club information'); 139 | } 140 | } 141 | }); 142 | 143 | // 10. Define information request tool schema 144 | const requestMoreInfoTool = new DynamicTool({ 145 | name: "RequestMoreInfo", 146 | description: "Handles customer support inquiries with smart routing based on urgency that aren't otherwise handled by other tools. If a user is asking for contact information, use this tool to handle it.", 147 | inputSchema: z.object({ 148 | topic: z.string(), 149 | urgency: z.enum(["low", "medium", "high"]).optional(), 150 | category: z.enum([ 151 | "product", 152 | "shipping", 153 | "technical", 154 | "wholesale", 155 | "other" 156 | ]), 157 | customerName: z.string().optional(), 158 | orderNumber: z.string().optional() 159 | }), 160 | 161 | async handler(input) { 162 | try { 163 | const result = await pool.query( 164 | 'SELECT * FROM support_categories WHERE name = $1', 165 | [input.category] 166 | ); 167 | 168 | const categoryData = result.rows[0]; 169 | const urgencyLevel = input.urgency || "medium"; 170 | 171 | // Core routing logic based on urgency 172 | let response = ""; 173 | if (urgencyLevel === "high") { 174 | response = `For urgent ${input.category} support, please call us at 1-888-12345-6789. We'll assist you immediately.`; 175 | } else if (urgencyLevel === "low") { 176 | response = `For ${input.category} support, please visit our self-service portal at coffeecompany.com/support/${input.category}`; 177 | } else { 178 | response = `For ${input.category} support, please email us at support@coffeecompany.com. We'll respond within ${categoryData.timeline}.`; 179 | } 180 | 181 | return new StringToolOutput(response); 182 | } catch (error) { 183 | console.error('Database error:', error); 184 | return new StringToolOutput('Sorry, we are experiencing technical difficulties. Please call us at 1-888-URGENT-CO for immediate assistance.'); 185 | } 186 | } 187 | }); 188 | 189 | // 12. Define store locations tool schema 190 | const getStoreLocationsTool = new DynamicTool({ 191 | name: "GetStoreLocations", 192 | description: "Provides information about store locations in different neighborhoods", 193 | inputSchema: z.object({ 194 | city: z.enum(["San Francisco", "Portland", "Seattle", "Chicago", "New York"]) 195 | }), 196 | 197 | // 13. Implement store locations tool handler 198 | async handler(input) { 199 | try { 200 | // 13.1. Query store locations 201 | const result = await pool.query( 202 | 'SELECT * FROM store_locations WHERE city = $1', 203 | [input.city] 204 | ); 205 | 206 | // 13.2. Handle no locations case 207 | if (result.rows.length === 0) { 208 | return new StringToolOutput( 209 | `We currently don't have any store locations in ${input.city}. ` + 210 | `Please check back later as we're expanding! ` + 211 | `You can visit our online store 24/7 at www.coffeecompany.com` 212 | ); 213 | } 214 | 215 | // 13.3. Format location information 216 | return new StringToolOutput( 217 | result.rows.map(loc => 218 | `${loc.neighborhood} Location:\n` + 219 | `Address: ${loc.address}\n` + 220 | `Hours: ${loc.hours}\n` + 221 | `Specialties: ${loc.specialties}\n` + 222 | `Parking: ${loc.parking}\n` 223 | ).join("\n\n") 224 | ); 225 | } catch (error) { 226 | console.error('Database error:', error); 227 | throw new Error('Failed to retrieve store location information'); 228 | } 229 | }, 230 | }); 231 | 232 | // 14. Initialize BeeAgent with configured tools 233 | const agent = new BeeAgent({ 234 | llm, 235 | memory: new UnconstrainedMemory(), 236 | tools: [ 237 | getBrewingGuideTool, 238 | getShippingEstimateTool, 239 | getStoreLocationsTool, 240 | handleCoffeeClubTool, 241 | requestMoreInfoTool 242 | ], 243 | }); 244 | 245 | // 15. Define question handling logic 246 | async function handleAskEndpoint(req: Request, res: Response) { 247 | try { 248 | // 15.1. Extract and validate question 249 | const { question } = req.body as { question?: string }; 250 | 251 | if (!question) { 252 | return res.status(400).json({ error: 'Question is required' }); 253 | } 254 | 255 | // 15.2. Initialize conversation tracking 256 | const steps: Array<{ type: string; content: string }> = []; 257 | 258 | // 15.3. Process question through agent 259 | const response = await agent 260 | .run( 261 | { 262 | prompt: `You are a coffee expert and customer service representative. The customer asks: ${question}. You do not have the ability for orders, but you can answer general questions about the company and coffee.` 263 | }, 264 | { 265 | execution: { 266 | maxRetriesPerStep: 5, 267 | totalMaxRetries: 10, 268 | maxIterations: 15, 269 | }, 270 | }, 271 | ) 272 | .observe((emitter) => { 273 | // 15.4. Handle different types of events 274 | emitter.on("error", ({ error }) => { 275 | console.log(`Error: `, FrameworkError.ensure(error).dump()); 276 | steps.push({ 277 | type: "error", 278 | content: FrameworkError.ensure(error).dump() 279 | }); 280 | }); 281 | emitter.on("retry", () => { 282 | steps.push({ 283 | type: "retry", 284 | content: "Retrying" 285 | }); 286 | }); 287 | emitter.on("update", ({ update }) => { 288 | steps.push({ 289 | type: update.key, 290 | content: update.value 291 | }); 292 | }); 293 | }); 294 | 295 | // 15.5. Return response 296 | return res.status(200).json({ 297 | answer: response.result?.text ?? "No answer available.", 298 | steps: steps 299 | }); 300 | } catch (error) { 301 | // 15.6. Handle errors 302 | if (error instanceof Error) { 303 | console.error(FrameworkError.ensure(error).dump()); 304 | return res.status(500).json({ error: 'Internal server error' }); 305 | } else { 306 | console.error('Unknown error:', error); 307 | return res.status(500).json({ error: 'Unknown error occurred' }); 308 | } 309 | } 310 | } 311 | 312 | // 16. Configure API endpoints 313 | app.post('/ask', async (req: Request, res: Response) => { 314 | await handleAskEndpoint(req, res); 315 | }); 316 | 317 | // 17. Initialize server 318 | app.listen(3008, () => { 319 | console.log('Server is running on port 3008'); 320 | }); 321 | 322 | // 18. Example test questions for reference 323 | // 18.1. "How long will shipping take to Europe?" 324 | // 18.2. "What's the best way to brew pour-over coffee for 4 people?" 325 | // 18.3. "Tell me about your Ethiopian coffees" 326 | // 18.4. "Where is the NYC location?" 327 | -------------------------------------------------------------------------------- /agent/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bee-backend", 3 | "module": "index.ts", 4 | "type": "module", 5 | "devDependencies": { 6 | "@types/bun": "latest" 7 | }, 8 | "peerDependencies": { 9 | "typescript": "^5.0.0" 10 | } 11 | } -------------------------------------------------------------------------------- /agent/schema.sql: -------------------------------------------------------------------------------- 1 | -- Create tables for coffee shop database 2 | 3 | -- Brewing Methods 4 | CREATE TABLE brewing_methods ( 5 | id SERIAL PRIMARY KEY, 6 | name VARCHAR(50) UNIQUE NOT NULL, 7 | grind_size VARCHAR(50) NOT NULL, 8 | water_temp VARCHAR(50) NOT NULL, 9 | brew_time VARCHAR(50) NOT NULL 10 | ); 11 | 12 | -- Brewing Ratios 13 | CREATE TABLE brewing_ratios ( 14 | id SERIAL PRIMARY KEY, 15 | strength VARCHAR(20) NOT NULL, 16 | ratio INTEGER NOT NULL 17 | ); 18 | 19 | -- Store Locations 20 | CREATE TABLE store_locations ( 21 | id SERIAL PRIMARY KEY, 22 | city VARCHAR(100) NOT NULL, 23 | neighborhood VARCHAR(100) NOT NULL, 24 | address VARCHAR(255) NOT NULL, 25 | hours VARCHAR(100) NOT NULL, 26 | specialties TEXT NOT NULL, 27 | parking TEXT NOT NULL 28 | ); 29 | 30 | -- Shipping Regions 31 | CREATE TABLE shipping_regions ( 32 | id SERIAL PRIMARY KEY, 33 | name VARCHAR(100) UNIQUE NOT NULL, 34 | standard_delivery VARCHAR(100) NOT NULL, 35 | express_delivery VARCHAR(100) NOT NULL, 36 | cost TEXT NOT NULL, 37 | notes TEXT NOT NULL 38 | ); 39 | 40 | -- Support Categories 41 | CREATE TABLE support_categories ( 42 | id SERIAL PRIMARY KEY, 43 | name VARCHAR(50) UNIQUE NOT NULL, 44 | message TEXT NOT NULL, 45 | timeline VARCHAR(100) NOT NULL, 46 | additional_info TEXT NOT NULL 47 | ); 48 | 49 | -- Coffee Club Benefits 50 | CREATE TABLE coffee_club_benefits ( 51 | id SERIAL PRIMARY KEY, 52 | benefit TEXT NOT NULL 53 | ); 54 | -------------------------------------------------------------------------------- /agent/seed.sql: -------------------------------------------------------------------------------- 1 | -- Seed brewing methods 2 | INSERT INTO brewing_methods (name, grind_size, water_temp, brew_time) VALUES 3 | ('pourover', 'medium-fine', '195-205°F', '2:30-3:00'), 4 | ('french-press', 'coarse', '200-205°F', '4:00'), 5 | ('espresso', 'fine', '195-200°F', '25-30 seconds'), 6 | ('cold-brew', 'coarse', 'room temperature', '12-24 hours'), 7 | ('aeropress', 'fine-medium', '185-205°F', '1:30-2:00'), 8 | ('moka-pot', 'fine', 'off boil', '3:00-4:00'); 9 | 10 | -- Seed brewing ratios 11 | INSERT INTO brewing_ratios (strength, ratio) VALUES 12 | ('light', 18), 13 | ('medium', 16), 14 | ('strong', 14); 15 | 16 | -- Seed store locations 17 | INSERT INTO store_locations (city, neighborhood, address, hours, specialties, parking) VALUES 18 | ('San Francisco', 'Mission District', '123 Valencia St', '7AM-7PM daily', 'Pour-over bar, roasting facility tours', 'Street parking available'), 19 | ('San Francisco', 'Hayes Valley', '456 Hayes St', '6AM-6PM daily', 'Espresso tasting flights, pastry program', 'Public garage nearby'), 20 | ('Portland', 'Pearl District', '789 Pearl St', '6AM-8PM daily', 'Cold brew tower, brewing workshops', 'Free lot parking'), 21 | ('Seattle', 'Capitol Hill', '321 Pike St', '6AM-8PM daily', 'Single origin flights, roasting demos', 'Street parking and nearby garage'), 22 | ('Chicago', 'Wicker Park', '555 Milwaukee Ave', '7AM-7PM daily', 'Coffee education center, tasting room', 'Street parking available'), 23 | ('New York', 'Williamsburg', '888 Bedford Ave', '6AM-8PM daily', 'Specialty drinks, brewing classes', 'Street parking, limited'); 24 | 25 | -- Seed shipping regions 26 | INSERT INTO shipping_regions (name, standard_delivery, express_delivery, cost, notes) VALUES 27 | ('USA-West', '2-3 business days', 'Next day delivery', 'Free over $35, otherwise $4.99', 'Orders placed before 2PM PST ship same day'), 28 | ('USA-Central', '3-4 business days', '2 day delivery', 'Free over $35, otherwise $4.99', 'Orders placed before 2PM CST ship same day'), 29 | ('USA-East', '3-5 business days', '2-3 day delivery', 'Free over $35, otherwise $4.99', 'Orders placed before 2PM EST ship same day'), 30 | ('Canada', '5-7 business days', '3-4 business days', 'Free over $49 CAD, otherwise $9.99 CAD', 'Duties and taxes included in price'), 31 | ('Europe', '7-10 business days', '4-5 business days', 'Free over €50, otherwise €12.99', 'VAT calculated at checkout'), 32 | ('Asia', '10-14 business days', '5-7 business days', 'Free over $75 USD, otherwise $19.99 USD', 'Import duties may apply'), 33 | ('Australia', '10-14 business days', '7-8 business days', 'Free over $75 AUD, otherwise $14.99 AUD', 'GST included in price'), 34 | ('South-America', '12-15 business days', '7-9 business days', '$24.99 USD flat rate', 'Import duties may apply'), 35 | ('Other', '15-20 business days', '10-12 business days', '$29.99 USD flat rate', 'Import duties may apply'); 36 | 37 | -- Seed support categories 38 | INSERT INTO support_categories (name, message, timeline, additional_info) VALUES 39 | ('product', 'We''ll have our coffee experts get back to you with detailed information about our products.', 'within 24 hours', 'In the meantime, you might want to check our coffee guide section on our website.'), 40 | ('shipping', 'Our logistics team will provide you with specific shipping information for your location.', 'within 12 hours', 'You can also track existing orders on our website using your order number.'), 41 | ('technical', 'Our technical support team will help you resolve any issues you''re experiencing.', 'within 48 hours', 'For immediate brewing tips, check our troubleshooting guide online.'), 42 | ('wholesale', 'Our wholesale team will contact you with partnership information.', 'within 2 business days', 'You can review our wholesale program details on our website.'), 43 | ('other', 'We''ll have our customer support team address your inquiry.', 'within 24 hours', 'Our FAQ section might have the information you''re looking for.'); 44 | 45 | -- Seed coffee club benefits 46 | INSERT INTO coffee_club_benefits (benefit) VALUES 47 | ('Fresh roasted coffee delivered on your schedule'), 48 | ('Member-only discounts'), 49 | ('Early access to new roasts'), 50 | ('Free shipping on all deliveries'); 51 | -------------------------------------------------------------------------------- /agent/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Enable latest features 4 | "lib": ["ESNext", "DOM"], 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", 9 | "allowJs": true, 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "noEmit": true, 16 | 17 | // Best practices 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noFallthroughCasesInSwitch": true, 21 | 22 | // Some stricter flags (disabled by default) 23 | "noUnusedLocals": false, 24 | "noUnusedParameters": false, 25 | "noPropertyAccessFromIndexSignature": false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /react-ui/bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developersdigest/bee-agent/2d4575eaabddbd51c73facc352a98705e8a360e5/react-ui/bun.lockb -------------------------------------------------------------------------------- /react-ui/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/index.css", 9 | "baseColor": "slate", 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 | } -------------------------------------------------------------------------------- /react-ui/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import globals from "globals"; 3 | import reactHooks from "eslint-plugin-react-hooks"; 4 | import reactRefresh from "eslint-plugin-react-refresh"; 5 | import tseslint from "typescript-eslint"; 6 | 7 | export default tseslint.config( 8 | { ignores: ["dist"] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ["**/*.{ts,tsx}"], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | "react-hooks": reactHooks, 18 | "react-refresh": reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | "react-refresh/only-export-components": [ 23 | "warn", 24 | { allowConstantExport: true }, 25 | ], 26 | "@typescript-eslint/no-unused-vars": "off", 27 | }, 28 | } 29 | ); 30 | -------------------------------------------------------------------------------- /react-ui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | chatterbee-ux 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /react-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite_react_shadcn_ts", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "build:dev": "vite build --mode development", 10 | "lint": "eslint .", 11 | "preview": "vite preview" 12 | }, 13 | "dependencies": { 14 | "@hookform/resolvers": "^3.9.0", 15 | "@radix-ui/react-accordion": "^1.2.0", 16 | "@radix-ui/react-alert-dialog": "^1.1.1", 17 | "@radix-ui/react-aspect-ratio": "^1.1.0", 18 | "@radix-ui/react-avatar": "^1.1.0", 19 | "@radix-ui/react-checkbox": "^1.1.1", 20 | "@radix-ui/react-collapsible": "^1.1.0", 21 | "@radix-ui/react-context-menu": "^2.2.1", 22 | "@radix-ui/react-dialog": "^1.1.2", 23 | "@radix-ui/react-dropdown-menu": "^2.1.1", 24 | "@radix-ui/react-hover-card": "^1.1.1", 25 | "@radix-ui/react-label": "^2.1.0", 26 | "@radix-ui/react-menubar": "^1.1.1", 27 | "@radix-ui/react-navigation-menu": "^1.2.0", 28 | "@radix-ui/react-popover": "^1.1.1", 29 | "@radix-ui/react-progress": "^1.1.0", 30 | "@radix-ui/react-radio-group": "^1.2.0", 31 | "@radix-ui/react-scroll-area": "^1.1.0", 32 | "@radix-ui/react-select": "^2.1.1", 33 | "@radix-ui/react-separator": "^1.1.0", 34 | "@radix-ui/react-slider": "^1.2.0", 35 | "@radix-ui/react-slot": "^1.1.0", 36 | "@radix-ui/react-switch": "^1.1.0", 37 | "@radix-ui/react-tabs": "^1.1.0", 38 | "@radix-ui/react-toast": "^1.2.1", 39 | "@radix-ui/react-toggle": "^1.1.0", 40 | "@radix-ui/react-toggle-group": "^1.1.0", 41 | "@radix-ui/react-tooltip": "^1.1.4", 42 | "@tanstack/react-query": "^5.56.2", 43 | "class-variance-authority": "^0.7.1", 44 | "clsx": "^2.1.1", 45 | "cmdk": "^1.0.0", 46 | "date-fns": "^3.6.0", 47 | "embla-carousel-react": "^8.3.0", 48 | "input-otp": "^1.2.4", 49 | "leaflet": "^1.9.4", 50 | "lucide-react": "^0.462.0", 51 | "next-themes": "^0.3.0", 52 | "react": "^18.3.1", 53 | "react-day-picker": "^8.10.1", 54 | "react-dom": "^18.3.1", 55 | "react-hook-form": "^7.53.0", 56 | "react-leaflet": "^4.2.1", 57 | "react-resizable-panels": "^2.1.3", 58 | "react-router-dom": "^6.26.2", 59 | "recharts": "^2.12.7", 60 | "sonner": "^1.5.0", 61 | "tailwind-merge": "^2.5.2", 62 | "tailwindcss-animate": "^1.0.7", 63 | "vaul": "^0.9.3", 64 | "zod": "^3.23.8" 65 | }, 66 | "devDependencies": { 67 | "@eslint/js": "^9.9.0", 68 | "@tailwindcss/typography": "^0.5.15", 69 | "@types/node": "^22.5.5", 70 | "@types/react": "^18.3.3", 71 | "@types/react-dom": "^18.3.0", 72 | "@vitejs/plugin-react-swc": "^3.5.0", 73 | "autoprefixer": "^10.4.20", 74 | "eslint": "^9.9.0", 75 | "eslint-plugin-react-hooks": "^5.1.0-rc.0", 76 | "eslint-plugin-react-refresh": "^0.4.9", 77 | "globals": "^15.9.0", 78 | "lovable-tagger": "^1.0.19", 79 | "postcss": "^8.4.47", 80 | "tailwindcss": "^3.4.11", 81 | "typescript": "^5.5.3", 82 | "typescript-eslint": "^8.0.1", 83 | "vite": "^5.4.1" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /react-ui/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /react-ui/public/placeholder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /react-ui/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /react-ui/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Toaster } from "@/components/ui/toaster"; 2 | import { Toaster as Sonner } from "@/components/ui/sonner"; 3 | import { TooltipProvider } from "@/components/ui/tooltip"; 4 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 5 | import { BrowserRouter, Routes, Route } from "react-router-dom"; 6 | import Index from "./pages/Index"; 7 | 8 | const queryClient = new QueryClient(); 9 | 10 | const App = () => ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | } /> 18 | 19 | 20 | 21 | 22 | ); 23 | 24 | export default App; 25 | -------------------------------------------------------------------------------- /react-ui/src/components/ChatMessage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { cn } from '@/lib/utils'; 3 | import { MapPin, ExternalLink, Coffee, ShoppingCart } from 'lucide-react'; 4 | 5 | interface ChatMessageProps { 6 | message: { 7 | type: 'user' | 'bot'; 8 | content: string; 9 | steps?: Array<{ 10 | type: string; 11 | content: string; 12 | }>; 13 | }; 14 | } 15 | 16 | interface OrderDetails { 17 | productId: string; 18 | productName: string; 19 | price: number; 20 | description: string; 21 | quantity: number; 22 | } 23 | 24 | const ChatMessage = ({ message }: ChatMessageProps) => { 25 | const extractStoreDetails = (steps: Array<{ type: string; content: string }>) => { 26 | const storeStep = steps.find(step => step.type === 'tool_input' && step.content.includes('latitude')); 27 | 28 | if (storeStep) { 29 | try { 30 | return JSON.parse(storeStep.content); 31 | } catch { 32 | return null; 33 | } 34 | } 35 | return null; 36 | }; 37 | 38 | const hasStoreLocation = message.steps?.some(step => 39 | step.type === 'tool_name' && step.content === 'FindStoreLocation' 40 | ); 41 | 42 | const storeDetails = hasStoreLocation && message.steps ? extractStoreDetails(message.steps) : null; 43 | 44 | const extractOrderDetails = (steps: Array<{ type: string; content: string }>) => { 45 | const orderStep = steps.find(step => step.type === 'tool_input' && step.content.includes('productId')); 46 | 47 | if (orderStep) { 48 | try { 49 | return JSON.parse(orderStep.content); 50 | } catch { 51 | return null; 52 | } 53 | } 54 | return null; 55 | }; 56 | 57 | const hasOrderDetails = message.steps?.some(step => 58 | step.type === 'tool_name' && step.content === 'OrderCoffee' 59 | ); 60 | 61 | const orderDetails = hasOrderDetails && message.steps ? extractOrderDetails(message.steps) : null; 62 | 63 | return ( 64 |
65 | {storeDetails && ( 66 |
67 |
68 | 69 |
70 |

Store Location

71 | {Object.entries(storeDetails) 72 | .filter(([key]) => key !== 'latitude' && key !== 'longitude') 73 | .map(([key, value]) => ( 74 |

75 | {`${key.charAt(0).toUpperCase() + key.slice(1)}: ${value}`} 76 |

77 | ))} 78 |
79 | 85 | 86 | Open in Maps 87 | 88 |
89 |
90 | )} 91 | {orderDetails && ( 92 |
93 |
94 | 95 |
96 |

Order Details

97 |
98 |

{orderDetails.productName}

99 |

${orderDetails.price.toFixed(2)}

100 |

{orderDetails.description}

101 |
102 | 109 |
110 |
111 |
112 |
113 |
114 | )} 115 |
123 | {message.content} 124 |
125 |
126 | ); 127 | }; 128 | 129 | export default ChatMessage; -------------------------------------------------------------------------------- /react-ui/src/components/FeedbackForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button } from '@/components/ui/button'; 3 | import { Textarea } from '@/components/ui/textarea'; 4 | import { useToast } from '@/components/ui/use-toast'; 5 | 6 | interface FeedbackFormProps { 7 | orderId: string; 8 | } 9 | 10 | const FeedbackForm = ({ orderId }: FeedbackFormProps) => { 11 | const [feedback, setFeedback] = useState(''); 12 | const { toast } = useToast(); 13 | 14 | const handleSubmit = async () => { 15 | try { 16 | const response = await fetch('http://localhost:3008/ask', { 17 | method: 'POST', 18 | headers: { 'Content-Type': 'application/json' }, 19 | body: JSON.stringify({ 20 | question: `Submit feedback for order ${orderId}: ${feedback}` 21 | }), 22 | }); 23 | 24 | if (response.ok) { 25 | toast({ 26 | title: "Feedback Submitted", 27 | description: "Thank you for your feedback!", 28 | }); 29 | setFeedback(''); 30 | } 31 | } catch (error) { 32 | toast({ 33 | title: "Error", 34 | description: "Failed to submit feedback. Please try again.", 35 | variant: "destructive", 36 | }); 37 | } 38 | }; 39 | 40 | return ( 41 |
42 |

Provide Feedback

43 |