├── .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 |
53 | );
54 | };
55 |
56 | export default FeedbackForm;
--------------------------------------------------------------------------------
/react-ui/src/components/ProductCard.tsx:
--------------------------------------------------------------------------------
1 | import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card";
2 | import { Button } from "@/components/ui/button";
3 |
4 | interface ProductCardProps {
5 | title: string;
6 | description: string;
7 | price: string;
8 | imageUrl: string;
9 | }
10 |
11 | export const ProductCard = ({ title, description, price, imageUrl }: ProductCardProps) => {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 | {title}
19 | {description}
20 |
21 |
22 | {price}
23 |
26 |
27 |
28 | );
29 | };
--------------------------------------------------------------------------------
/react-ui/src/components/StoreFooter.tsx:
--------------------------------------------------------------------------------
1 | export const StoreFooter = () => {
2 | return (
3 |
40 | );
41 | };
--------------------------------------------------------------------------------
/react-ui/src/components/StoreHeader.tsx:
--------------------------------------------------------------------------------
1 | import { ShoppingCart, Coffee } from "lucide-react";
2 | import { Button } from "@/components/ui/button";
3 |
4 | export const StoreHeader = () => {
5 | return (
6 |
29 | );
30 | };
--------------------------------------------------------------------------------
/react-ui/src/components/ui/accordion.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as AccordionPrimitive from "@radix-ui/react-accordion"
3 | import { ChevronDown } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Accordion = AccordionPrimitive.Root
8 |
9 | const AccordionItem = React.forwardRef<
10 | React.ElementRef,
11 | React.ComponentPropsWithoutRef
12 | >(({ className, ...props }, ref) => (
13 |
18 | ))
19 | AccordionItem.displayName = "AccordionItem"
20 |
21 | const AccordionTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, children, ...props }, ref) => (
25 |
26 | svg]:rotate-180",
30 | className
31 | )}
32 | {...props}
33 | >
34 | {children}
35 |
36 |
37 |
38 | ))
39 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
40 |
41 | const AccordionContent = React.forwardRef<
42 | React.ElementRef,
43 | React.ComponentPropsWithoutRef
44 | >(({ className, children, ...props }, ref) => (
45 |
50 | {children}
51 |
52 | ))
53 |
54 | AccordionContent.displayName = AccordionPrimitive.Content.displayName
55 |
56 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
57 |
--------------------------------------------------------------------------------
/react-ui/src/components/ui/alert-dialog.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
3 |
4 | import { cn } from "@/lib/utils"
5 | import { buttonVariants } from "@/components/ui/button"
6 |
7 | const AlertDialog = AlertDialogPrimitive.Root
8 |
9 | const AlertDialogTrigger = AlertDialogPrimitive.Trigger
10 |
11 | const AlertDialogPortal = AlertDialogPrimitive.Portal
12 |
13 | const AlertDialogOverlay = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef
16 | >(({ className, ...props }, ref) => (
17 |
25 | ))
26 | AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
27 |
28 | const AlertDialogContent = React.forwardRef<
29 | React.ElementRef,
30 | React.ComponentPropsWithoutRef
31 | >(({ className, ...props }, ref) => (
32 |
33 |
34 |
42 |
43 | ))
44 | AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
45 |
46 | const AlertDialogHeader = ({
47 | className,
48 | ...props
49 | }: React.HTMLAttributes) => (
50 |
57 | )
58 | AlertDialogHeader.displayName = "AlertDialogHeader"
59 |
60 | const AlertDialogFooter = ({
61 | className,
62 | ...props
63 | }: React.HTMLAttributes) => (
64 |
71 | )
72 | AlertDialogFooter.displayName = "AlertDialogFooter"
73 |
74 | const AlertDialogTitle = React.forwardRef<
75 | React.ElementRef,
76 | React.ComponentPropsWithoutRef
77 | >(({ className, ...props }, ref) => (
78 |
83 | ))
84 | AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
85 |
86 | const AlertDialogDescription = React.forwardRef<
87 | React.ElementRef,
88 | React.ComponentPropsWithoutRef
89 | >(({ className, ...props }, ref) => (
90 |
95 | ))
96 | AlertDialogDescription.displayName =
97 | AlertDialogPrimitive.Description.displayName
98 |
99 | const AlertDialogAction = React.forwardRef<
100 | React.ElementRef,
101 | React.ComponentPropsWithoutRef
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
110 |
111 | const AlertDialogCancel = React.forwardRef<
112 | React.ElementRef,
113 | React.ComponentPropsWithoutRef
114 | >(({ className, ...props }, ref) => (
115 |
124 | ))
125 | AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
126 |
127 | export {
128 | AlertDialog,
129 | AlertDialogPortal,
130 | AlertDialogOverlay,
131 | AlertDialogTrigger,
132 | AlertDialogContent,
133 | AlertDialogHeader,
134 | AlertDialogFooter,
135 | AlertDialogTitle,
136 | AlertDialogDescription,
137 | AlertDialogAction,
138 | AlertDialogCancel,
139 | }
140 |
--------------------------------------------------------------------------------
/react-ui/src/components/ui/alert.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const alertVariants = cva(
7 | "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8 | {
9 | variants: {
10 | variant: {
11 | default: "bg-background text-foreground",
12 | destructive:
13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14 | },
15 | },
16 | defaultVariants: {
17 | variant: "default",
18 | },
19 | }
20 | )
21 |
22 | const Alert = React.forwardRef<
23 | HTMLDivElement,
24 | React.HTMLAttributes & VariantProps
25 | >(({ className, variant, ...props }, ref) => (
26 |
32 | ))
33 | Alert.displayName = "Alert"
34 |
35 | const AlertTitle = React.forwardRef<
36 | HTMLParagraphElement,
37 | React.HTMLAttributes
38 | >(({ className, ...props }, ref) => (
39 |
44 | ))
45 | AlertTitle.displayName = "AlertTitle"
46 |
47 | const AlertDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | AlertDescription.displayName = "AlertDescription"
58 |
59 | export { Alert, AlertTitle, AlertDescription }
60 |
--------------------------------------------------------------------------------
/react-ui/src/components/ui/aspect-ratio.tsx:
--------------------------------------------------------------------------------
1 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
2 |
3 | const AspectRatio = AspectRatioPrimitive.Root
4 |
5 | export { AspectRatio }
6 |
--------------------------------------------------------------------------------
/react-ui/src/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const Avatar = React.forwardRef<
7 | React.ElementRef,
8 | React.ComponentPropsWithoutRef
9 | >(({ className, ...props }, ref) => (
10 |
18 | ))
19 | Avatar.displayName = AvatarPrimitive.Root.displayName
20 |
21 | const AvatarImage = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, ...props }, ref) => (
25 |
30 | ))
31 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
32 |
33 | const AvatarFallback = React.forwardRef<
34 | React.ElementRef,
35 | React.ComponentPropsWithoutRef
36 | >(({ className, ...props }, ref) => (
37 |
45 | ))
46 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
47 |
48 | export { Avatar, AvatarImage, AvatarFallback }
49 |
--------------------------------------------------------------------------------
/react-ui/src/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default:
12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17 | outline: "text-foreground",
18 | },
19 | },
20 | defaultVariants: {
21 | variant: "default",
22 | },
23 | }
24 | )
25 |
26 | export interface BadgeProps
27 | extends React.HTMLAttributes,
28 | VariantProps {}
29 |
30 | function Badge({ className, variant, ...props }: BadgeProps) {
31 | return (
32 |
33 | )
34 | }
35 |
36 | export { Badge, badgeVariants }
37 |
--------------------------------------------------------------------------------
/react-ui/src/components/ui/breadcrumb.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { ChevronRight, MoreHorizontal } from "lucide-react"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const Breadcrumb = React.forwardRef<
8 | HTMLElement,
9 | React.ComponentPropsWithoutRef<"nav"> & {
10 | separator?: React.ReactNode
11 | }
12 | >(({ ...props }, ref) => )
13 | Breadcrumb.displayName = "Breadcrumb"
14 |
15 | const BreadcrumbList = React.forwardRef<
16 | HTMLOListElement,
17 | React.ComponentPropsWithoutRef<"ol">
18 | >(({ className, ...props }, ref) => (
19 |
27 | ))
28 | BreadcrumbList.displayName = "BreadcrumbList"
29 |
30 | const BreadcrumbItem = React.forwardRef<
31 | HTMLLIElement,
32 | React.ComponentPropsWithoutRef<"li">
33 | >(({ className, ...props }, ref) => (
34 |
39 | ))
40 | BreadcrumbItem.displayName = "BreadcrumbItem"
41 |
42 | const BreadcrumbLink = React.forwardRef<
43 | HTMLAnchorElement,
44 | React.ComponentPropsWithoutRef<"a"> & {
45 | asChild?: boolean
46 | }
47 | >(({ asChild, className, ...props }, ref) => {
48 | const Comp = asChild ? Slot : "a"
49 |
50 | return (
51 |
56 | )
57 | })
58 | BreadcrumbLink.displayName = "BreadcrumbLink"
59 |
60 | const BreadcrumbPage = React.forwardRef<
61 | HTMLSpanElement,
62 | React.ComponentPropsWithoutRef<"span">
63 | >(({ className, ...props }, ref) => (
64 |
72 | ))
73 | BreadcrumbPage.displayName = "BreadcrumbPage"
74 |
75 | const BreadcrumbSeparator = ({
76 | children,
77 | className,
78 | ...props
79 | }: React.ComponentProps<"li">) => (
80 | svg]:size-3.5", className)}
84 | {...props}
85 | >
86 | {children ?? }
87 |
88 | )
89 | BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
90 |
91 | const BreadcrumbEllipsis = ({
92 | className,
93 | ...props
94 | }: React.ComponentProps<"span">) => (
95 |
101 |
102 | More
103 |
104 | )
105 | BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
106 |
107 | export {
108 | Breadcrumb,
109 | BreadcrumbList,
110 | BreadcrumbItem,
111 | BreadcrumbLink,
112 | BreadcrumbPage,
113 | BreadcrumbSeparator,
114 | BreadcrumbEllipsis,
115 | }
116 |
--------------------------------------------------------------------------------
/react-ui/src/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
4 |
5 | import { cn } from "@/lib/utils"
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | },
22 | size: {
23 | default: "h-10 px-4 py-2",
24 | sm: "h-9 rounded-md px-3",
25 | lg: "h-11 rounded-md px-8",
26 | icon: "h-10 w-10",
27 | },
28 | },
29 | defaultVariants: {
30 | variant: "default",
31 | size: "default",
32 | },
33 | }
34 | )
35 |
36 | export interface ButtonProps
37 | extends React.ButtonHTMLAttributes,
38 | VariantProps {
39 | asChild?: boolean
40 | }
41 |
42 | const Button = React.forwardRef(
43 | ({ className, variant, size, asChild = false, ...props }, ref) => {
44 | const Comp = asChild ? Slot : "button"
45 | return (
46 |
51 | )
52 | }
53 | )
54 | Button.displayName = "Button"
55 |
56 | export { Button, buttonVariants }
57 |
--------------------------------------------------------------------------------
/react-ui/src/components/ui/calendar.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { ChevronLeft, ChevronRight } from "lucide-react";
3 | import { DayPicker } from "react-day-picker";
4 |
5 | import { cn } from "@/lib/utils";
6 | import { buttonVariants } from "@/components/ui/button";
7 |
8 | export type CalendarProps = React.ComponentProps;
9 |
10 | function Calendar({
11 | className,
12 | classNames,
13 | showOutsideDays = true,
14 | ...props
15 | }: CalendarProps) {
16 | return (
17 | ,
56 | IconRight: ({ ..._props }) => ,
57 | }}
58 | {...props}
59 | />
60 | );
61 | }
62 | Calendar.displayName = "Calendar";
63 |
64 | export { Calendar };
65 |
--------------------------------------------------------------------------------
/react-ui/src/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ))
45 | CardTitle.displayName = "CardTitle"
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | CardDescription.displayName = "CardDescription"
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ))
65 | CardContent.displayName = "CardContent"
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ))
77 | CardFooter.displayName = "CardFooter"
78 |
79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
80 |
--------------------------------------------------------------------------------
/react-ui/src/components/ui/carousel.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import useEmblaCarousel, {
3 | type UseEmblaCarouselType,
4 | } from "embla-carousel-react"
5 | import { ArrowLeft, ArrowRight } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 | import { Button } from "@/components/ui/button"
9 |
10 | type CarouselApi = UseEmblaCarouselType[1]
11 | type UseCarouselParameters = Parameters
12 | type CarouselOptions = UseCarouselParameters[0]
13 | type CarouselPlugin = UseCarouselParameters[1]
14 |
15 | type CarouselProps = {
16 | opts?: CarouselOptions
17 | plugins?: CarouselPlugin
18 | orientation?: "horizontal" | "vertical"
19 | setApi?: (api: CarouselApi) => void
20 | }
21 |
22 | type CarouselContextProps = {
23 | carouselRef: ReturnType[0]
24 | api: ReturnType[1]
25 | scrollPrev: () => void
26 | scrollNext: () => void
27 | canScrollPrev: boolean
28 | canScrollNext: boolean
29 | } & CarouselProps
30 |
31 | const CarouselContext = React.createContext(null)
32 |
33 | function useCarousel() {
34 | const context = React.useContext(CarouselContext)
35 |
36 | if (!context) {
37 | throw new Error("useCarousel must be used within a ")
38 | }
39 |
40 | return context
41 | }
42 |
43 | const Carousel = React.forwardRef<
44 | HTMLDivElement,
45 | React.HTMLAttributes & CarouselProps
46 | >(
47 | (
48 | {
49 | orientation = "horizontal",
50 | opts,
51 | setApi,
52 | plugins,
53 | className,
54 | children,
55 | ...props
56 | },
57 | ref
58 | ) => {
59 | const [carouselRef, api] = useEmblaCarousel(
60 | {
61 | ...opts,
62 | axis: orientation === "horizontal" ? "x" : "y",
63 | },
64 | plugins
65 | )
66 | const [canScrollPrev, setCanScrollPrev] = React.useState(false)
67 | const [canScrollNext, setCanScrollNext] = React.useState(false)
68 |
69 | const onSelect = React.useCallback((api: CarouselApi) => {
70 | if (!api) {
71 | return
72 | }
73 |
74 | setCanScrollPrev(api.canScrollPrev())
75 | setCanScrollNext(api.canScrollNext())
76 | }, [])
77 |
78 | const scrollPrev = React.useCallback(() => {
79 | api?.scrollPrev()
80 | }, [api])
81 |
82 | const scrollNext = React.useCallback(() => {
83 | api?.scrollNext()
84 | }, [api])
85 |
86 | const handleKeyDown = React.useCallback(
87 | (event: React.KeyboardEvent) => {
88 | if (event.key === "ArrowLeft") {
89 | event.preventDefault()
90 | scrollPrev()
91 | } else if (event.key === "ArrowRight") {
92 | event.preventDefault()
93 | scrollNext()
94 | }
95 | },
96 | [scrollPrev, scrollNext]
97 | )
98 |
99 | React.useEffect(() => {
100 | if (!api || !setApi) {
101 | return
102 | }
103 |
104 | setApi(api)
105 | }, [api, setApi])
106 |
107 | React.useEffect(() => {
108 | if (!api) {
109 | return
110 | }
111 |
112 | onSelect(api)
113 | api.on("reInit", onSelect)
114 | api.on("select", onSelect)
115 |
116 | return () => {
117 | api?.off("select", onSelect)
118 | }
119 | }, [api, onSelect])
120 |
121 | return (
122 |
135 |
143 | {children}
144 |
145 |
146 | )
147 | }
148 | )
149 | Carousel.displayName = "Carousel"
150 |
151 | const CarouselContent = React.forwardRef<
152 | HTMLDivElement,
153 | React.HTMLAttributes
154 | >(({ className, ...props }, ref) => {
155 | const { carouselRef, orientation } = useCarousel()
156 |
157 | return (
158 |
169 | )
170 | })
171 | CarouselContent.displayName = "CarouselContent"
172 |
173 | const CarouselItem = React.forwardRef<
174 | HTMLDivElement,
175 | React.HTMLAttributes
176 | >(({ className, ...props }, ref) => {
177 | const { orientation } = useCarousel()
178 |
179 | return (
180 |
191 | )
192 | })
193 | CarouselItem.displayName = "CarouselItem"
194 |
195 | const CarouselPrevious = React.forwardRef<
196 | HTMLButtonElement,
197 | React.ComponentProps
198 | >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
199 | const { orientation, scrollPrev, canScrollPrev } = useCarousel()
200 |
201 | return (
202 |
220 | )
221 | })
222 | CarouselPrevious.displayName = "CarouselPrevious"
223 |
224 | const CarouselNext = React.forwardRef<
225 | HTMLButtonElement,
226 | React.ComponentProps
227 | >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
228 | const { orientation, scrollNext, canScrollNext } = useCarousel()
229 |
230 | return (
231 |
249 | )
250 | })
251 | CarouselNext.displayName = "CarouselNext"
252 |
253 | export {
254 | type CarouselApi,
255 | Carousel,
256 | CarouselContent,
257 | CarouselItem,
258 | CarouselPrevious,
259 | CarouselNext,
260 | }
261 |
--------------------------------------------------------------------------------
/react-ui/src/components/ui/chart.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as RechartsPrimitive from "recharts"
3 |
4 | import { cn } from "@/lib/utils"
5 |
6 | // Format: { THEME_NAME: CSS_SELECTOR }
7 | const THEMES = { light: "", dark: ".dark" } as const
8 |
9 | export type ChartConfig = {
10 | [k in string]: {
11 | label?: React.ReactNode
12 | icon?: React.ComponentType
13 | } & (
14 | | { color?: string; theme?: never }
15 | | { color?: never; theme: Record }
16 | )
17 | }
18 |
19 | type ChartContextProps = {
20 | config: ChartConfig
21 | }
22 |
23 | const ChartContext = React.createContext(null)
24 |
25 | function useChart() {
26 | const context = React.useContext(ChartContext)
27 |
28 | if (!context) {
29 | throw new Error("useChart must be used within a ")
30 | }
31 |
32 | return context
33 | }
34 |
35 | const ChartContainer = React.forwardRef<
36 | HTMLDivElement,
37 | React.ComponentProps<"div"> & {
38 | config: ChartConfig
39 | children: React.ComponentProps<
40 | typeof RechartsPrimitive.ResponsiveContainer
41 | >["children"]
42 | }
43 | >(({ id, className, children, config, ...props }, ref) => {
44 | const uniqueId = React.useId()
45 | const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
46 |
47 | return (
48 |
49 |
58 |
59 |
60 | {children}
61 |
62 |
63 |
64 | )
65 | })
66 | ChartContainer.displayName = "Chart"
67 |
68 | const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
69 | const colorConfig = Object.entries(config).filter(
70 | ([_, config]) => config.theme || config.color
71 | )
72 |
73 | if (!colorConfig.length) {
74 | return null
75 | }
76 |
77 | return (
78 |