Compare commits

..

No commits in common. "feat/Agent" and "main" have entirely different histories.

36 changed files with 570 additions and 2141 deletions

View File

@ -1,36 +0,0 @@
# EditorConfig is awesome: https://EditorConfig.org
# Top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
# TypeScript and JavaScript files
[*.{ts,tsx,js,jsx}]
indent_size = 4
max_line_length = 120
# JSON files
[*.json]
indent_size = 2
# YAML files
[*.{yml,yaml}]
indent_size = 2
# Markdown files
[*.md]
trim_trailing_whitespace = false
max_line_length = off
# Package files
[{package.json,package-lock.json}]
indent_size = 2

2
.env.example Normal file
View File

@ -0,0 +1,2 @@
# API Configuration
# VITE_API_BASE_URL=http://localhost:3000

43
.gitattributes vendored
View File

@ -1,43 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto
# Source code files
*.ts text eol=lf
*.tsx text eol=lf
*.js text eol=lf
*.jsx text eol=lf
*.json text eol=lf
*.css text eol=lf
*.scss text eol=lf
*.html text eol=lf
*.md text eol=lf
*.yml text eol=lf
*.yaml text eol=lf
# Configuration files
*.config.js text eol=lf
*.config.ts text eol=lf
.editorconfig text eol=lf
.gitignore text eol=lf
.gitattributes text eol=lf
# Shell scripts
*.sh text eol=lf
# Binary files
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.svg binary
*.woff binary
*.woff2 binary
*.ttf binary
*.eot binary
# Archives
*.zip binary
*.tar binary
*.gz binary

99
.gitignore vendored
View File

@ -1,16 +1,5 @@
# Dependencies
node_modules/
.pnp
.pnp.js
# Production builds
dist/
dist-ssr/
build/
*.local
# Logs
logs/
logs
*.log
npm-debug.log*
yarn-debug.log*
@ -18,88 +7,18 @@ yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
.env*.local
node_modules
dist
dist-ssr
*.local
# Testing
coverage/
.nyc_output/
*.lcov
.jest/
# IDE and Editor files
.vscode/
.idea/
*.swp
*.swo
*~
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# OS files
Thumbs.db
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Desktop.ini
# Temporary files
*.tmp
*.temp
.cache/
.parcel-cache/
.turbo/
# TypeScript
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# Storybook build outputs
storybook-static/
# Vite
.vite/
# Debug
.vscode-test/
# Local Netlify folder
.netlify
# Sentry
.sentryclirc
# Documentation files (internal only, not for GitHub)
GUIDELINES.md
CONTRIBUTING.md
# Lock files (optional - uncomment if you want to ignore)
# package-lock.json
# yarn.lock
# pnpm-lock.yaml

292
GUIDELINES.md Normal file
View File

@ -0,0 +1,292 @@
# AgenticIQ Coding Standards and Best Practices
## 1. Introduction and Purpose
This document establishes comprehensive coding standards and best practices for all developers working on the AgenticIQ enterprise application. Following these guidelines ensures code consistency, maintainability, and scalability across the entire development team.
### 1.1 Document Objectives
- Establish uniform coding standards across all development teams
- Reduce technical debt and improve code quality
- Facilitate easier onboarding of new team members
- Enable efficient code reviews and collaboration
- Ensure application performs optimally across all devices
### 1.2 Technology Stack
AgenticIQ is built using modern enterprise technologies. All code must align with the architectural patterns and technology constraints defined for this application.
## 2. General Coding Standards
### 2.1 Code Formatting Rules
1. Use 4 spaces for indentation (no tabs)
2. Maximum line length: 120 characters
3. Use UTF-8 encoding for all source files
4. End files with a single newline character
5. Remove trailing whitespace from all lines
6. Use consistent brace style (opening brace on same line for JS/TS, new line for C#)
### 2.2 Code Organization Principles
- Single Responsibility Principle (SRP): Each file/class/function should have one purpose
- DRY (Don't Repeat Yourself): Extract common logic into reusable functions/components
- KISS (Keep It Simple): Avoid over-engineering; prefer clarity over cleverness
- YAGNI (You Aren't Gonna Need It): Don't add functionality until it's required
### 2.3 File Size Guidelines
- Maximum file length: 400 lines (excluding comments)
- Maximum function/method length: 50 lines
- Maximum class length: 300 lines
- If limits are exceeded, refactor into smaller units
## 3. Naming Conventions
Consistent naming is critical for code readability and maintainability. All naming must be meaningful, descriptive, and follow the patterns defined below.
### 3.1 General Naming Rules
1. Use meaningful, descriptive names that reveal intent
2. Avoid single-letter names except for loop counters (i, j, k)
3. Avoid abbreviations unless universally understood (e.g., URL, HTTP, ID)
4. Use American English spelling consistently
5. Boolean variables should indicate true/false (isActive, hasPermission, canEdit)
### 3.2 Naming Convention Reference Table
| Element | Convention | Example | Notes |
| --------------- | -------------- | --------------------- | --------------------------- |
| Class/Interface | PascalCase | UserService, IRepository | Prefix I for interfaces (C#) |
| Function/Method | camelCase | getUserById, calculateTotal | Start with verb |
| Variable | camelCase | userName, orderItems | Descriptive nouns |
| Constant | SCREAMING_SNAKE| MAX_RETRY_COUNT | All uppercase |
| Private Field | _camelCase | _userRepository | Underscore prefix |
| Component | PascalCase | UserDashboard.tsx | Match filename |
| CSS Class | kebab-case | user-profile-card | BEM methodology |
| File (General) | kebab-case | user-service.ts | Lowercase |
| Database Table | PascalCase Singular | UserProfile, OrderItem | Match entity name |
| API Endpoint | kebab-case plural | /api/user-profiles | RESTful conventions |
### 3.3 Function Naming Patterns
Function names should clearly describe the action performed. Use consistent verb prefixes to indicate the operation type.
| Prefix | Purpose | Examples |
| ---------- | ------------------ | ---------------------------------------------- |
| get | Retrieve data | getUserById(), getOrderList(), getActiveUsers()|
| set | Assign value | setUserName(), setOrderStatus() |
| create | Create new entity | createUser(), createOrder(), createReport() |
| update | Modify existing | updateUserProfile(), updateOrderQuantity() |
| delete/remove | Remove data | deleteUser(), removeCartItem() |
| is/has/can | Boolean checks | isActive(), hasPermission(), canEdit() |
| validate | Check validity | validateEmail(), validateUserInput() |
| calculate | Compute values | calculateTotal(), calculateDiscount() |
| handle | Event handling | handleSubmit(), handleClick(), handleError() |
| fetch/load | Async data load | fetchUserData(), loadConfiguration() |
| render | UI rendering | renderHeader(), renderUserList() |
| format/parse | Data transformation | formatDate(), parseJson(), formatCurrency() |
## 4. Function Design Guidelines
### 4.1 Function Structure Best Practices
1. Single Purpose: Each function should do exactly one thing well
2. Parameter Limit: Maximum 4 parameters; use objects for more
3. Pure Functions: Prefer functions without side effects when possible
4. Early Returns: Use guard clauses to handle edge cases first
5. Avoid Deep Nesting: Maximum 3 levels of nesting; refactor if deeper
### 4.2 Function Signature Guidelines
- Return Type: Always specify explicit return types in TypeScript
- Parameter Types: Use strong typing; avoid `any`
- Default Values: Provide sensible defaults for optional parameters
- Async Functions: Return `Promise<T>` with proper typing
### 4.3 Function Documentation Template
Every public function must include JSDoc/XML documentation with:
- `@description` - Brief explanation of what the function does
- `@param {Type} name` - Description of each parameter
- `@returns {Type}` - Description of the return value
- `@throws {ErrorType}` - Exceptions that may be thrown
- `@example` - Usage example (for complex functions)
## 5. Responsive Design & Screen Optimization
### 5.1 Breakpoint Standards
| Device | Breakpoint | CSS Variable | Target Devices |
| -------------- | ---------------- | --------------- | ------------------- |
| Mobile S | < 375px | --screen-xs | Small phones |
| Mobile M | 375px - 424px | --screen-sm | Standard phones |
| Mobile L | 425px - 767px | --screen-md | Large phones |
| Tablet | 768px - 1023px | --screen-lg | iPad, Tablets |
| Laptop | 1024px - 1439px | --screen-xl | Laptops, small desktops |
| Desktop | 1440px - 1919px | --screen-xxl | Standard monitors |
| Large Display | ≥ 1920px | --screen-xxxl | Large/4K monitors |
### 5.2 Mobile-First Development
1. Start with mobile layout and progressively enhance for larger screens
2. Use min-width media queries to add complexity as screen size increases
3. Design touch-friendly interfaces with minimum 44px touch targets
4. Optimize images for mobile using srcset and responsive images
5. Test on actual devices, not just browser developer tools
### 5.3 Layout Guidelines
- Flexbox: Use for one-dimensional layouts (rows or columns)
- CSS Grid: Use for two-dimensional layouts and complex designs
- Container Queries: Use for component-level responsiveness
- Relative Units: Use rem/em for typography, % for widths, vh/vw for viewports
- Avoid fixed pixel widths for containers; prefer max-width constraints
### 5.4 Typography Scaling
| Element | Mobile | Tablet | Desktop |
| ------- | ------------- | ------------- | ------------- |
| H1 | 1.75rem (28px)| 2rem (32px) | 2.5rem (40px) |
| H2 | 1.5rem (24px) | 1.75rem (28px)| 2rem (32px) |
| H3 | 1.25rem (20px)| 1.375rem (22px)| 1.5rem (24px)|
| Body | 0.875rem (14px)| 0.9375rem (15px)| 1rem (16px) |
| Caption | 0.75rem (12px)| 0.8125rem (13px)| 0.875rem (14px)|
## 6. Code Architecture Principles
### 6.1 Folder Structure Standards
Maintain consistent folder organization across all modules:
- `/src/components`: Reusable UI components
- `/src/pages`: Route-level page components
- `/src/hooks`: Custom React hooks
- `/src/services`: API calls and external integrations
- `/src/utils`: Utility functions and helpers
- `/src/types`: TypeScript type definitions
- `/src/constants`: Application constants and enums
- `/src/styles`: Global styles and theme configuration
### 6.2 Component Design Patterns
1. Container/Presentational Pattern: Separate logic from presentation
2. Compound Components: Create flexible, composable component APIs
3. Render Props/HOCs: Share behavior between components
4. Custom Hooks: Extract reusable stateful logic
5. Context Pattern: Share state across component tree
### 6.3 State Management Guidelines
- Local State: Use `useState` for component-specific state
- Server State: Use React Query/TanStack Query for API data
- Global State: Use Zustand/Redux for app-wide state
- Form State: Use React Hook Form for complex forms
## 7. Error Handling Best Practices
### 7.1 Error Handling Principles
1. Fail Fast: Detect and report errors as early as possible
2. Be Specific: Use specific error types, not generic exceptions
3. Log Appropriately: Include context but exclude sensitive data
4. User-Friendly Messages: Show helpful messages, not stack traces
5. Recover Gracefully: Implement fallback behavior where possible
### 7.2 Error Categories
| Error Type | Handling Strategy | Example |
| -------------- | ------------------------------ | ------------------------ |
| Validation | Show inline field errors | Invalid email format |
| Network | Retry with exponential backoff | API timeout |
| Authentication | Redirect to login | Token expired |
| Authorization | Show access denied message | 403 Forbidden |
| Not Found | Show 404 page or fallback | Resource not found |
| Server Error | Show error page with support contact | 500 Internal Error |
### 7.3 Try-Catch Guidelines
- Catch specific errors, not generic Exception/Error
- Never swallow exceptions silently without logging
- Use finally blocks for cleanup operations
- Avoid try-catch for flow control
- Re-throw with additional context when appropriate
## 8. Performance Optimization
### 8.1 Frontend Performance
1. Code Splitting: Use dynamic imports for route-level splitting
2. Lazy Loading: Defer loading of below-the-fold content
3. Image Optimization: Use WebP format, responsive images, lazy loading
4. Bundle Analysis: Regularly analyze and optimize bundle size
5. Caching: Implement proper caching headers and service workers
6. Memoization: Use `useMemo`, `useCallback`, `React.memo` appropriately
### 8.2 React Performance Tips
- Use virtualization for long lists (react-window, react-virtualized)
- Avoid inline function definitions in render
- Use stable keys for list items
- Avoid unnecessary re-renders with proper state structure
- Profile with React DevTools before optimizing
### 8.3 Performance Metrics Targets
| Metric | Target | Measurement |
| ------------------------ | --------------- | -------------------- |
| First Contentful Paint (FCP) | < 1.8 seconds | Lighthouse |
| Largest Contentful Paint (LCP) | < 2.5 seconds | Lighthouse |
| Cumulative Layout Shift (CLS) | < 0.1 | Lighthouse |
| Time to Interactive (TTI) | < 3.8 seconds | Lighthouse |
| Bundle Size (main) | < 250KB gzipped | Webpack analyzer |
## 9. Security Guidelines
### 9.1 Input Validation & Sanitization
1. Validate all inputs on both client and server side
2. Sanitize user inputs to prevent XSS attacks
3. Use parameterized queries to prevent SQL injection
4. Implement rate limiting on API endpoints
5. Validate file uploads (type, size, content)
### 9.2 Authentication & Authorization
- Never store sensitive data in localStorage/sessionStorage
- Use HTTP-only, Secure cookies for session tokens
- Implement proper CORS configuration
- Use CSRF tokens for state-changing operations
- Always verify authorization on the server
### 9.3 Data Protection
- Never log sensitive information (passwords, tokens, PII)
- Use environment variables for secrets
- Encrypt sensitive data at rest and in transit
- Implement proper error messages (no stack traces in production)
- Follow principle of least privilege
## 10. Documentation Standards
### 10.1 Code Comments Guidelines
- WHY, not WHAT: Explain the reasoning, not what the code does
- Self-Documenting Code: Use clear names; add comments only when necessary
- Update Comments: Keep comments in sync with code changes
- TODO Comments: Include ticket number, e.g., `// TODO(JIRA-123): Fix edge case`
- Avoid obvious comments
### 10.2 README Requirements
Every project/module must have a README.md with:
- Project overview and purpose
- Prerequisites and dependencies
- Installation and setup instructions
- Environment configuration
- Available scripts and commands
- Contributing guidelines
### 10.3 API Documentation
All APIs must be documented using OpenAPI/Swagger specification with endpoint descriptions, request/response schemas, authentication requirements, error codes and responses, and usage examples.
## 11. Version Control Guidelines
### 11.1 Git Commit Message Format
Follow the Conventional Commits specification: `<type>(<scope>): <subject>`
| Type | Description |
| ------ | --------------------------------------------- |
| feat | New feature for the user |
| fix | Bug fix for the user |
| docs | Documentation only changes |
| style | Formatting, missing semicolons (no code change) |
| refactor | Code change that neither fixes a bug nor adds a feature |
| test | Adding or updating tests |
| chore | Build process or auxiliary tool changes |
### 11.2 Branching Strategy
- `main`: Production-ready code only
- `develop`: Integration branch for features
- `feature/JIRA-XXX-description`: Feature branches
- `bugfix/JIRA-XXX-description`: Bug fix branches
- `hotfix/JIRA-XXX-description`: Production hotfixes
- `release/vX.X.X`: Release preparation branches
## 12. Testing Standards
### 12.1 Testing Pyramid
- Unit Tests (70%): Test individual functions/components in isolation
- Integration Tests (20%): Test component interactions and API integrations
- E2E Tests (10%): Test critical user journeys end-to-end
### 12.2 Test Naming Convention
Use the pattern: describe what is being tested + under what conditions + expected outcome.
- Example: `getUserById returns user object when valid ID is provided`
- Example: `calculateTotal throws error when items array is empty`
### 12.3 Code Coverage Requirements
- Minimum line coverage: 80%
- Minimum branch coverage: 75%
- Critical paths: 100% coverage required
- New code must maintain or improve coverage

View File

@ -125,19 +125,12 @@ All functions include JSDoc comments with:
## 🤝 Contributing
We welcome contributions! Please read our [Contributing Guide](CONTRIBUTING.md) for details on:
Follow the AgenticIQ Enterprise Coding Guidelines when contributing:
- Git workflow and branching strategy
- Commit message format (Conventional Commits)
- Code standards and guidelines
- Pull request process
Quick checklist:
1. Use conventional commits (`feat:`, `fix:`, `docs:`, etc.)
2. Follow branching strategy (`feature/`, `bugfix/`, `hotfix/`)
3. Maintain code quality standards (see `GUIDELINES.md`)
3. Maintain code quality standards
4. Add tests for new features
5. Ensure all checks pass before submitting PR
## 📄 License

View File

@ -1,3 +0,0 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.25 1.125C8.25 0.50368 7.74632 0 7.125 0C6.50368 0 6 0.50368 6 1.125V6H1.125C0.50368 6 0 6.50368 0 7.125C0 7.74632 0.50368 8.25 1.125 8.25H6V13.125C6 13.7463 6.50368 14.25 7.125 14.25C7.74632 14.25 8.25 13.7463 8.25 13.125V8.25H13.125C13.7463 8.25 14.25 7.74632 14.25 7.125C14.25 6.50368 13.7463 6 13.125 6H8.25V1.125Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 449 B

View File

@ -1,10 +0,0 @@
<svg width="140" height="141" viewBox="0 0 140 141" fill="none" xmlns="http://www.w3.org/2000/svg">
<ellipse opacity="0.1" cx="64" cy="136" rx="46.5" ry="4.5" fill="#2B2A7F"/>
<path d="M15.6504 73.3227C15.6504 74.4272 16.5458 75.3227 17.6504 75.3227C18.755 75.3227 19.6504 74.4272 19.6504 73.3227H17.6504H15.6504ZM113.85 73.3227H111.85C111.85 98.783 91.2107 119.423 65.7504 119.423V121.423V123.423C93.4199 123.423 115.85 100.992 115.85 73.3227H113.85ZM17.6504 73.3227H19.6504C19.6504 47.8623 40.2901 27.2227 65.7504 27.2227V25.2227V23.2227C38.0809 23.2227 15.6504 45.6532 15.6504 73.3227H17.6504ZM65.7504 25.2227V27.2227C91.2107 27.2227 111.85 47.8623 111.85 73.3227H113.85H115.85C115.85 45.6532 93.4199 23.2227 65.7504 23.2227V25.2227Z" fill="black"/>
<rect x="11.25" y="63.2539" width="16" height="20" rx="4" fill="#F7F8FF" stroke="#0033FF" stroke-width="4"/>
<rect x="104.25" y="63.2539" width="16" height="20" rx="4" fill="#F7F8FF" stroke="#0033FF" stroke-width="4"/>
<rect x="73.25" y="116.254" width="9" height="14" rx="4.5" transform="rotate(90 73.25 116.254)" fill="white" stroke="#0033FF" stroke-width="4"/>
<path d="M64.8779 54.123C67.5621 54.123 70.0705 54.6241 72.4023 55.627C74.7341 56.6297 76.7763 58.0073 78.5283 59.7598C80.2808 61.5118 81.6583 63.5528 82.6611 65.8818C83.6639 68.2105 84.165 70.7196 84.165 73.4082C84.165 78.4051 82.8151 83.0208 80.1143 87.2549C77.4132 91.4887 73.991 94.896 69.8496 97.4766C69.5408 97.6388 69.2317 97.7281 68.9229 97.7441C68.614 97.7606 68.3292 97.6917 68.0693 97.5371C67.8101 97.3829 67.5912 97.1834 67.4121 96.9395C67.2329 96.6952 67.1304 96.398 67.1055 96.0488L66.9209 92.6924H64.8809C59.5112 92.6924 54.9542 90.821 51.2109 87.0781C47.4676 83.3358 45.5958 78.7808 45.5957 73.4131C45.5957 68.0447 47.4671 63.4869 51.21 59.7412C54.9532 55.9957 59.5093 54.1231 64.8779 54.123ZM64.8809 57.7393C60.5016 57.7393 56.7951 59.2557 53.7617 62.2891C50.7284 65.3224 49.2119 69.0289 49.2119 73.4082C49.212 77.7873 50.7286 81.4931 53.7617 84.5264C56.7951 87.5597 60.5016 89.0771 64.8809 89.0771H70.9072V92.3311C73.7597 89.9205 76.0803 87.0984 77.8682 83.8643C79.656 80.6301 80.5498 77.1445 80.5498 73.4082C80.5498 69.0289 79.0324 65.3224 75.999 62.2891C72.9658 59.2559 69.2599 57.7393 64.8809 57.7393Z" fill="black"/>
<path d="M64.8779 54.123C67.5621 54.123 70.0705 54.6241 72.4023 55.627C74.7341 56.6297 76.7763 58.0073 78.5283 59.7598C80.2808 61.5118 81.6583 63.5528 82.6611 65.8818C83.6639 68.2105 84.165 70.7196 84.165 73.4082C84.165 78.4051 82.8151 83.0208 80.1143 87.2549C77.4132 91.4887 73.991 94.896 69.8496 97.4766C69.5408 97.6388 69.2317 97.7281 68.9229 97.7441C68.614 97.7606 68.3292 97.6917 68.0693 97.5371C67.8101 97.3829 67.5912 97.1834 67.4121 96.9395C67.2329 96.6952 67.1304 96.398 67.1055 96.0488L66.9209 92.6924H64.8809C59.5112 92.6924 54.9542 90.821 51.2109 87.0781C47.4676 83.3358 45.5958 78.7808 45.5957 73.4131C45.5957 68.0447 47.4671 63.4869 51.21 59.7412C54.9532 55.9957 59.5093 54.1231 64.8779 54.123ZM64.8809 57.7393C60.5016 57.7393 56.7951 59.2557 53.7617 62.2891C50.7284 65.3224 49.2119 69.0289 49.2119 73.4082C49.212 77.7873 50.7286 81.4931 53.7617 84.5264C56.7951 87.5597 60.5016 89.0771 64.8809 89.0771H70.9072V92.3311C73.7597 89.9205 76.0803 87.0984 77.8682 83.8643C79.656 80.6301 80.5498 77.1445 80.5498 73.4082C80.5498 69.0289 79.0324 65.3224 75.999 62.2891C72.9658 59.2559 69.2599 57.7393 64.8809 57.7393Z" stroke="black"/>
<path d="M65.75 82.5999C65.3375 82.5999 64.9844 82.4531 64.6908 82.1594C64.3969 81.8656 64.25 81.5124 64.25 81.0999C64.25 80.6874 64.3969 80.3343 64.6908 80.0407C64.9844 79.7468 65.3375 79.5999 65.75 79.5999C66.1625 79.5999 66.5156 79.7468 66.8092 80.0407C67.1031 80.3343 67.25 80.6874 67.25 81.0999C67.25 81.5124 67.1031 81.8656 66.8092 82.1594C66.5156 82.4531 66.1625 82.5999 65.75 82.5999ZM64.3848 77.6962V66.2539H67.1152V77.6962H64.3848Z" fill="#0033FF"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -1,3 +0,0 @@
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.91667 12.25C2.59583 12.25 2.32118 12.1358 2.09271 11.9073C1.86424 11.6788 1.75 11.4042 1.75 11.0833V2.91667C1.75 2.59583 1.86424 2.32118 2.09271 2.09271C2.32118 1.86424 2.59583 1.75 2.91667 1.75H6.41667C6.58194 1.75 6.72049 1.8059 6.83229 1.91771C6.9441 2.02951 7 2.16806 7 2.33333C7 2.49861 6.9441 2.63715 6.83229 2.74896C6.72049 2.86076 6.58194 2.91667 6.41667 2.91667H2.91667V11.0833H11.0833V7.58333C11.0833 7.41806 11.1392 7.27951 11.251 7.16771C11.3628 7.0559 11.5014 7 11.6667 7C11.8319 7 11.9705 7.0559 12.0823 7.16771C12.1941 7.27951 12.25 7.41806 12.25 7.58333V11.0833C12.25 11.4042 12.1358 11.6788 11.9073 11.9073C11.6788 12.1358 11.4042 12.25 11.0833 12.25H2.91667ZM11.0833 3.73333L6.06667 8.75C5.95972 8.85694 5.82361 8.91042 5.65833 8.91042C5.49306 8.91042 5.35694 8.85694 5.25 8.75C5.14306 8.64306 5.08958 8.50694 5.08958 8.34167C5.08958 8.17639 5.14306 8.04028 5.25 7.93333L10.2667 2.91667H8.75C8.58472 2.91667 8.44618 2.86076 8.33437 2.74896C8.22257 2.63715 8.16667 2.49861 8.16667 2.33333C8.16667 2.16806 8.22257 2.02951 8.33437 1.91771C8.44618 1.8059 8.58472 1.75 8.75 1.75H11.6667C11.8319 1.75 11.9705 1.8059 12.0823 1.91771C12.1941 2.02951 12.25 2.16806 12.25 2.33333V5.25C12.25 5.41528 12.1941 5.55382 12.0823 5.66563C11.9705 5.77743 11.8319 5.83333 11.6667 5.83333C11.5014 5.83333 11.3628 5.77743 11.251 5.66563C11.1392 5.55382 11.0833 5.41528 11.0833 5.25V3.73333Z" fill="#0033FF"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,4 +0,0 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 2.41602C14.1881 2.41602 17.5838 5.81101 17.584 9.99902C17.584 14.1872 14.1882 17.583 10 17.583C5.81199 17.5828 2.41699 14.1871 2.41699 9.99902C2.41717 5.81112 5.8121 2.41619 10 2.41602Z" stroke="black" stroke-opacity="0.5" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M18.333 18.334L15.833 15.834" stroke="black" stroke-opacity="0.5" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 550 B

View File

@ -1,19 +0,0 @@
<svg width="63" height="85" viewBox="0 0 63 85" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_1_27468" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="63" height="85">
<path d="M0 0H63V69C63 77.8366 55.8366 85 47 85H0V0Z" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_1_27468)">
<path d="M63.7743 1L17.5518 25.5215C11.753 28.5978 7.95639 34.7136 7.58789 41.5719L7.58789 85.5834H63.6215L63.7743 1Z" fill="url(#paint0_linear_1_27468)" fill-opacity="0.25"/>
<path d="M63.7743 13.1812L29.4387 34.6055C23.4302 38.3033 19.821 44.9802 19.9728 52.1178V85.5834L63.6215 85.5834L63.7743 13.1812Z" fill="url(#paint1_linear_1_27468)" fill-opacity="0.25"/>
</g>
<defs>
<linearGradient id="paint0_linear_1_27468" x1="13.5464" y1="72.2922" x2="70.9508" y2="28.0665" gradientUnits="userSpaceOnUse">
<stop stop-color="#00E2E0"/>
<stop offset="1" stop-color="#001C8D"/>
</linearGradient>
<linearGradient id="paint1_linear_1_27468" x1="13.5464" y1="72.2922" x2="70.9508" y2="28.0665" gradientUnits="userSpaceOnUse">
<stop stop-color="#00E2E0"/>
<stop offset="1" stop-color="#001C8D"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,19 +0,0 @@
<svg width="59" height="85" viewBox="0 0 59 85" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_1_27478" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="59" height="85">
<path d="M0 0H59V69C59 77.8366 51.8366 85 43 85H0V0Z" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_1_27478)">
<path d="M59.7743 1L13.5518 25.5215C7.75305 28.5978 3.95639 34.7136 3.5879 41.5719L3.58789 85.5834H59.6215L59.7743 1Z" fill="url(#paint0_linear_1_27478)" fill-opacity="0.25"/>
<path d="M59.7743 13.1812L25.4387 34.6055C19.4302 38.3033 15.821 44.9802 15.9728 52.1178V85.5834L59.6215 85.5834L59.7743 13.1812Z" fill="url(#paint1_linear_1_27478)" fill-opacity="0.25"/>
</g>
<defs>
<linearGradient id="paint0_linear_1_27478" x1="-2.00685" y1="74.0206" x2="73.6108" y2="-2.37143" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFB8C6"/>
<stop offset="1" stop-color="#596AC3"/>
</linearGradient>
<linearGradient id="paint1_linear_1_27478" x1="-2.00685" y1="74.0206" x2="73.6108" y2="-2.37143" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFB8C6"/>
<stop offset="1" stop-color="#596AC3"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,19 +0,0 @@
<svg width="59" height="85" viewBox="0 0 59 85" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_1_27498" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="59" height="85">
<path d="M0 0H59V69C59 77.8366 51.8366 85 43 85H0V0Z" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_1_27498)">
<path d="M59.7743 1L13.5518 25.5215C7.75305 28.5978 3.95639 34.7136 3.5879 41.5719L3.58789 85.5834H59.6215L59.7743 1Z" fill="url(#paint0_linear_1_27498)" fill-opacity="0.25"/>
<path d="M59.7743 13.1812L25.4387 34.6055C19.4302 38.3033 15.821 44.9802 15.9728 52.1178V85.5834L59.6215 85.5834L59.7743 13.1812Z" fill="url(#paint1_linear_1_27498)" fill-opacity="0.25"/>
</g>
<defs>
<linearGradient id="paint0_linear_1_27498" x1="3.99975" y1="79.5" x2="47.9644" y2="11.1068" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0CDFF"/>
<stop offset="1" stop-color="#0020A0"/>
</linearGradient>
<linearGradient id="paint1_linear_1_27498" x1="3.99975" y1="79.5" x2="47.9644" y2="11.1068" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0CDFF"/>
<stop offset="1" stop-color="#0020A0"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,19 +0,0 @@
<svg width="59" height="85" viewBox="0 0 59 85" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="mask0_1_27498" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="59" height="85">
<path d="M0 0H59V69C59 77.8366 51.8366 85 43 85H0V0Z" fill="#D9D9D9"/>
</mask>
<g mask="url(#mask0_1_27498)">
<path d="M59.7743 1L13.5518 25.5215C7.75305 28.5978 3.95639 34.7136 3.5879 41.5719L3.58789 85.5834H59.6215L59.7743 1Z" fill="url(#paint0_linear_1_27498)" fill-opacity="0.25"/>
<path d="M59.7743 13.1812L25.4387 34.6055C19.4302 38.3033 15.821 44.9802 15.9728 52.1178V85.5834L59.6215 85.5834L59.7743 13.1812Z" fill="url(#paint1_linear_1_27498)" fill-opacity="0.25"/>
</g>
<defs>
<linearGradient id="paint0_linear_1_27498" x1="3.99975" y1="79.5" x2="47.9644" y2="11.1068" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0CDFF"/>
<stop offset="1" stop-color="#0020A0"/>
</linearGradient>
<linearGradient id="paint1_linear_1_27498" x1="3.99975" y1="79.5" x2="47.9644" y2="11.1068" gradientUnits="userSpaceOnUse">
<stop stop-color="#F0CDFF"/>
<stop offset="1" stop-color="#0020A0"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -27,26 +27,22 @@ export function AgentCard({ title, tags, description }: AgentCardProps) {
];
return (
<div className="bg-white rounded-xl p-4 md:p-6 shadow-sm border border-gray-100 card-hover">
<h3 className="text-[1.25rem] md:text-[1.375rem] lg:text-[1.5rem] font-semibold text-gray-900 mb-2 md:mb-3 leading-tight">
{title}
</h3>
<div className="bg-white rounded-xl p-6 shadow-sm border border-gray-100 card-hover">
<h3 className="text-lg font-semibold text-gray-900 mb-3">{title}</h3>
<div className="flex flex-wrap gap-2 mb-3 md:mb-4">
<div className="flex flex-wrap gap-2 mb-4">
{tags.map((tag, index) => (
<span
key={index}
/* Use the index to pick the color; fall back to the first color if index > 2 */
className={`${tagColors[index] || tagColors[0]} px-2.5 py-1 md:px-3 text-[0.75rem] md:text-[0.8125rem] lg:text-[0.875rem] font-medium text-gray-700 border border-gray-200 rounded`}
className={`${tagColors[index] || tagColors[0]} px-3 py-1 text-xs font-medium text-gray-700 border border-gray-200`}
>
{tag}
</span>
))}
</div>
<p className="text-[0.875rem] md:text-[0.9375rem] lg:text-[1rem] text-gray-600 leading-relaxed">
{description}
</p>
<p className="text-sm text-gray-600 leading-relaxed">{description}</p>
</div>
);
}

View File

@ -1,117 +0,0 @@
/**
* Agents Section Component
* @description Displays agents list with tabs and pagination
*/
import { useState, useMemo, useEffect } from 'react';
import { AgentCard } from './agent-card';
import { Pagination } from './pagination';
import type { AgentData } from '@/constants/agents';
/**
* Agents section component props
*/
interface AgentsSectionProps {
/**
* List of agents to display
*/
agents: AgentData[];
/**
* Number of items per page
*/
itemsPerPage?: number;
}
/**
* Agents section component
* @description Displays agents with tabs and pagination
* @param props - Component props
* @returns AgentsSection element
*/
export function AgentsSection({ agents, itemsPerPage = 6 }: AgentsSectionProps) {
const [activeTab, setActiveTab] = useState<'all' | 'my'>('all');
const [currentPage, setCurrentPage] = useState(1);
/**
* Calculate pagination values
*/
const paginationData = useMemo(() => {
const totalItems = agents.length;
const totalPages = Math.ceil(totalItems / itemsPerPage);
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const paginatedAgents = agents.slice(startIndex, endIndex);
return {
totalPages,
paginatedAgents,
};
}, [agents, currentPage, itemsPerPage]);
/**
* Reset pagination when tab changes
*/
useEffect(() => {
setCurrentPage(1);
}, [activeTab]);
/**
* Handle page change
* @param page - New page number (1-indexed)
*/
const handlePageChange = (page: number) => {
setCurrentPage(page);
};
return (
<div className="bg-white rounded-xl md:rounded-2xl shadow-sm border border-gray-200 p-4 md:p-6 pb-20 md:pb-24">
{/* Tabs */}
<div className="flex gap-2 md:gap-4 mb-4 md:mb-6">
<button
onClick={() => setActiveTab('all')}
className={`px-4 py-2 md:px-6 md:py-2 rounded-lg text-[0.875rem] md:text-[1rem] font-medium transition-colors min-h-[44px] ${
activeTab === 'all'
? 'bg-black text-white'
: 'bg-white text-gray-600 border border-gray-300 hover:bg-gray-50'
}`}
>
All Agents
</button>
<button
onClick={() => setActiveTab('my')}
className={`px-4 py-2 md:px-6 md:py-2 rounded-lg text-[0.875rem] md:text-[1rem] font-medium transition-colors min-h-[44px] ${
activeTab === 'my'
? 'bg-black text-white'
: 'bg-white text-gray-600 border border-gray-300 hover:bg-gray-50'
}`}
>
My Agents
</button>
</div>
{/* Agent Cards Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 md:gap-6 mb-4 md:mb-6">
{paginationData.paginatedAgents.map((agent, index) => (
<AgentCard
key={`${agent.title}-${index}`}
title={agent.title}
tags={agent.tags}
description={agent.description}
/>
))}
</div>
{/* Fixed Pagination at Bottom */}
{paginationData.totalPages > 1 && (
<div className="fixed bottom-0 left-0 right-0 md:left-[88px] lg:left-[88px] md:mx-6 lg:mx-8 md:rounded-2xl bg-white border-t border-gray-200 px-4 py-3 md:px-6 lg:px-10 md:py-4 flex justify-end z-40 shadow-lg">
<Pagination
currentPage={currentPage}
totalPages={paginationData.totalPages}
onPageChange={handlePageChange}
/>
</div>
)}
</div>
);
}

View File

@ -6,5 +6,3 @@
export { Sidebar } from './sidebar';
export { MetricCard } from './metric-card';
export { AgentCard } from './agent-card';
export { Pagination } from './pagination';
export { AgentsSection } from './agents-section';

View File

@ -1,69 +1,72 @@
/**
* Metric Card Component
* @description Displays metric information with gradient background
*/
/**
* MetricCard component props
*/
interface MetricCardProps {
title: string;
subtitle: string;
value: string;
gradient: 'blue-teal' | 'pink-purple' | 'orange-yellow' | 'blue-purple';
imageUrl?: string;
}
type GradientStyle = {
backgroundImage?: string;
background?: string;
/**
* MetricCard component
* @description Card showing metric with gradient background and premium styling
* @param props - Component props
* @returns MetricCard element
*/
export function MetricCard({ title, subtitle, value, gradient }: MetricCardProps) {
const gradients = {
'blue-teal': ' bg-gradient-to-br from-[#001B2F] via-[#0626A8] to-[#039A98] text-white ',
'pink-purple': 'bg-gradient-to-br from-[#FF6B8B] via-[#944D8F] to-[#51459E]',
'orange-yellow': 'bg-gradient-to-br from-[#FF9D42] to-[#F17E31]',
'blue-purple': 'bg-gradient-to-br from-[#2D5BFF] to-[#8E34FF]',
};
export function MetricCard({ title, subtitle, value, gradient, imageUrl }: MetricCardProps) {
const gradientStyles: Record<MetricCardProps['gradient'], GradientStyle> = {
'blue-teal': {
backgroundImage: 'linear-gradient(16.03674192372833deg, rgba(0, 27, 47, 1) 10.549%, rgba(6, 38, 168, 1) 30.146%, rgba(3, 154, 152, 1) 77.418%)',
},
'pink-purple': {
background: 'linear-gradient(180deg, #ff708c 0%, #001377 100%)',
},
'orange-yellow': {
background: 'linear-gradient(180deg, #f3696e 0%, #f8a902 100%)',
},
'blue-purple': {
background: 'linear-gradient(180deg, #c336ff 0%, #0033ff 100%)',
},
};
// return (
// <div className={`${gradients[gradient]} rounded-[24px] p-6 text-white card-hover cursor-pointer relative overflow-hidden h-[120px] group transition-all duration-300`}>
// {/* Right side decorative vertical bar */}
// <div className="absolute right-0 top-0 bottom-0 w-[28%] bg-white/10 backdrop-blur-[2px]" />
const imageUrls: Record<MetricCardProps['gradient'], string> = {
'blue-teal': imageUrl || 'https://www.figma.com/api/mcp/asset/57e6c8be-018b-41d3-a71d-d59c115b529c',
'pink-purple': imageUrl || 'https://www.figma.com/api/mcp/asset/ccb9eb9b-4d2e-417d-b727-8dcd1586344a',
'orange-yellow': imageUrl || 'https://www.figma.com/api/mcp/asset/63c3ed6f-400a-431e-b3a7-43b6ff3d4eee',
'blue-purple': imageUrl || 'https://www.figma.com/api/mcp/asset/e5d86ccf-d5ce-4b5e-823a-a01acb631af7',
};
// <div className="flex justify-between items-center h-full relative z-10 font-['Poppins']">
// <div className="flex flex-col justify-center">
// <p className="text-[11px] font-semibold tracking-wider opacity-90 uppercase mb-0.5">
// {title}
// </p>
// <h3 className="text-[22px] font-bold leading-tight">
// {subtitle}
// </h3>
// </div>
// <div className="flex items-center justify-center min-w-[60px]">
// <span className="text-[48px] font-bold tracking-tighter leading-none">
// {value}
// </span>
// </div>
// </div>
// </div>
// );
return (
<div className="w-full h-full">
<div
className="flex flex-col justify-center min-h-[100px] sm:min-h-[110px] md:min-h-[120px] lg:min-h-[120px] p-4 sm:p-5 md:p-6 relative rounded-xl md:rounded-2xl overflow-hidden w-full h-full"
style={gradientStyles[gradient]}
>
{/* Text Content Container */}
<div className="flex items-center justify-between w-full z-10 text-white gap-2 sm:gap-3 md:gap-4">
<div className="flex flex-col items-start flex-1 min-w-0">
<p className="font-medium text-[0.625rem] sm:text-[0.6875rem] md:text-[0.75rem] lg:text-[0.875rem] uppercase opacity-80 leading-tight">
<div className={`p-6 h-[110px] rounded-2xl ${gradients[gradient]} w-full `}>
<div className="flex items-center justify-between w-full ">
<div className="flex flex-col">
<span className="text-[12px] font-medium text-white uppercase tracking-wider">
{title}
</p>
<p className="capitalize font-semibold text-[0.875rem] sm:text-[1rem] md:text-[1.125rem] lg:text-[1.25rem] truncate w-full mt-0.5 sm:mt-1">
</span>
<span className="text-[18px] font-bold mt-1 text-white">
{subtitle}
</p>
</span>
</div>
<p className="capitalize font-semibold text-[1.5rem] sm:text-[1.75rem] md:text-[2rem] lg:text-[2rem] ml-2 sm:ml-3 md:ml-4 flex-shrink-0">
<div className="text-5xl font-bold text-white">
{value}
</p>
</div>
{/* Image Container - Fixed to the right */}
<div className="absolute right-0 bottom-0 flex items-center h-full w-12 sm:w-16 md:w-20 pointer-events-none opacity-40 sm:opacity-50">
<img
alt=""
className="object-contain h-full w-full"
src={imageUrls[gradient]}
/>
</div>
</div>
</div>
);
}

View File

@ -1,163 +0,0 @@
import { ChevronLeft, ChevronRight } from 'lucide-react';
interface PaginationProps {
currentPage: number;
totalPages: number;
onPageChange: (page: number) => void;
}
/**
* Pagination component
* @description Displays pagination controls with page numbers and navigation arrows
* @param props - Component props
* @returns Pagination element
*/
export function Pagination({ currentPage, totalPages, onPageChange }: PaginationProps) {
/**
* Generate page numbers to display
* @returns Array of page numbers and ellipsis markers
*/
const getPageNumbers = (): (number | 'ellipsis')[] => {
const pages: (number | 'ellipsis')[] = [];
const maxVisible = 8;
if (totalPages <= maxVisible) {
// Show all pages if total is less than max visible
for (let i = 1; i <= totalPages; i++) {
pages.push(i);
}
} else {
// Always show first page
pages.push(1);
if (currentPage <= 3) {
// Near the beginning: show 1, 2, 3, ..., last
pages.push(2, 3);
if (totalPages > 4) {
pages.push('ellipsis');
}
pages.push(totalPages);
} else if (currentPage >= totalPages - 2) {
// Near the end: show 1, ..., last-2, last-1, last
pages.push('ellipsis');
pages.push(totalPages - 2, totalPages - 1, totalPages);
} else {
// In the middle: show 1, ..., current-1, current, current+1, ..., last
pages.push('ellipsis');
pages.push(currentPage - 1, currentPage, currentPage + 1);
if (currentPage + 1 < totalPages - 1) {
pages.push('ellipsis');
}
pages.push(totalPages);
}
}
return pages;
};
/**
* Handle previous page navigation
*/
const handlePrevious = () => {
if (currentPage > 1) {
onPageChange(currentPage - 1);
}
};
/**
* Handle next page navigation
*/
const handleNext = () => {
if (currentPage < totalPages) {
onPageChange(currentPage + 1);
}
};
/**
* Handle page number click
* @param page - Page number to navigate to
*/
const handlePageClick = (page: number) => {
if (page !== currentPage && page >= 1 && page <= totalPages) {
onPageChange(page);
}
};
const pageNumbers = getPageNumbers();
const isFirstPage = currentPage === 1;
const isLastPage = currentPage === totalPages;
return (
<div className="flex gap-2 md:gap-3 items-center justify-end rounded-[6px]">
{/* Previous Button */}
<button
onClick={handlePrevious}
disabled={isFirstPage}
className={`w-11 h-11 md:w-9 md:h-9 rounded-[6px] flex items-center justify-center transition-colors min-h-[44px] min-w-[44px] md:min-h-9 md:min-w-9 ${
isFirstPage
? 'bg-white border border-[#e1e4ef] cursor-not-allowed opacity-60'
: 'bg-white border border-[#e1e4ef] hover:bg-gray-50 cursor-pointer'
}`}
aria-label="Previous page"
>
<ChevronLeft className="w-4 h-4 text-[#4A4F5C]" />
</button>
{/* Page Numbers */}
{pageNumbers.map((page, index) => {
if (page === 'ellipsis') {
return (
<div
key={`ellipsis-${index}`}
className="w-11 h-11 md:w-9 md:h-9 rounded-[6px] bg-white border border-[rgba(0,51,255,0.25)] flex items-center justify-center min-h-[44px] min-w-[44px] md:min-h-9 md:min-w-9"
>
<span className="text-[0.75rem] md:text-[0.75rem] font-black text-[#03f] tracking-[1.44px] leading-none">
...
</span>
</div>
);
}
const isActive = page === currentPage;
return (
<button
key={page}
onClick={() => handlePageClick(page)}
className={`w-11 h-11 md:w-9 md:h-9 rounded-[6px] flex items-center justify-center transition-colors min-h-[44px] min-w-[44px] md:min-h-9 md:min-w-9 ${
isActive
? 'bg-[#03f] border border-[#03f]'
: 'bg-white border border-[rgba(0,51,255,0.25)] hover:bg-[#F5F8FF]'
}`}
aria-label={`Go to page ${page}`}
aria-current={isActive ? 'page' : undefined}
>
<span
className={`text-[0.875rem] md:text-[0.75rem] font-medium leading-none ${
isActive ? 'text-white' : 'text-[#03f]'
}`}
>
{page}
</span>
</button>
);
})}
{/* Next Button */}
<button
onClick={handleNext}
disabled={isLastPage}
className={`w-11 h-11 md:w-9 md:h-9 rounded-[6px] flex items-center justify-center transition-colors min-h-[44px] min-w-[44px] md:min-h-9 md:min-w-9 ${
isLastPage
? 'bg-white border border-[#e1e4ef] cursor-not-allowed opacity-60'
: 'bg-white border border-[#e1e4ef] hover:bg-gray-50 cursor-pointer'
}`}
aria-label="Next page"
>
<ChevronRight className="w-4 h-4 text-[#4A4F5C]" />
</button>
</div>
);
}

View File

@ -1,6 +1,9 @@
/**
* Dashboard Sidebar Component
* @description Vertical navigation sidebar with icons
*/
import { Home, Settings } from 'lucide-react';
import { Link } from '@tanstack/react-router';
import { APP_PATHS } from '@/routes';
/**
* Sidebar component
@ -9,57 +12,47 @@ import { APP_PATHS } from '@/routes';
*/
export function Sidebar() {
return (
<aside className="fixed left-4 top-20 bottom-4 w-14 md:left-6 md:top-24 md:bottom-6 md:w-16 bg-gradient-to-b from-[#00A1A0] to-[#00166D] flex flex-col items-center py-4 md:py-6 gap-4 md:gap-6 z-50 rounded-xl md:rounded-2xl shadow-xl hidden md:flex">
<aside className="fixed left-6 top-24 bottom-6 w-16 bg-gradient-to-b from-[#00A1A0] to-[#00166D] flex flex-col items-center py-6 gap-6 z-50 rounded-2xl shadow-xl">
{/* Navigation Icons */}
<nav className="flex flex-col items-center gap-5 flex-1 w-full">
{/* Home - Active State */}
<Link
to={APP_PATHS.dashboard}
className="sidebar-icon-v3 hover:bg-white/10"
activeProps={{ className: 'sidebar-icon-v3 bg-white/20 ring-1 ring-white/40' }}
aria-label="Home"
>
<Home className="w-4 h-4 md:w-5 md:h-5 text-white" />
</Link>
<button className="w-[42px] h-[42px] bg-white/20 rounded-xl flex items-center justify-center text-white shadow-inner transition-all hover:bg-white/30" aria-label="Home">
<Home className="w-5 h-5 text-white" />
</button>
<Link
to={APP_PATHS.agent}
className="sidebar-icon-v3 hover:bg-white/10"
activeProps={{ className: 'sidebar-icon-v3 bg-white/20 ring-1 ring-white/40' }}
aria-label="Agent"
>
<img src="/Agent.svg" alt="Agent" className="w-9 h-9 md:w-[42px] md:h-[42px]" />
</Link>
<button className="sidebar-icon-v3" aria-label="Agent">
<img src="/Agent.svg" alt="Agent" className="w-[42px] h-[42px] " />
</button>
<button className="sidebar-icon-v3" aria-label="KB">
<img src="/KB.svg" alt="KB" className="w-9 h-9 md:w-[42px] md:h-[42px]" />
<img src="/KB.svg" alt="KB" className="w-[42px] h-[42px] " />
</button>
<button className="sidebar-icon-v3" aria-label="Models">
<img src="/Models.svg" alt="Models" className="w-9 h-9 md:w-[42px] md:h-[42px]" />
<img src="/Models.svg" alt="Models" className="w-[42px] h-[42px] " />
</button>
<button className="sidebar-icon-v3" aria-label="Workflow">
<img src="/Workflow.svg" alt="Workflow" className="w-9 h-9 md:w-[42px] md:h-[42px]" />
<img src="/Workflow.svg" alt="Workflow" className="w-[42px] h-[42px] " />
</button>
<button className="sidebar-icon-v3" aria-label="Nav Item">
<img src="/Nav item.svg" alt="Nav Item" className="w-9 h-9 md:w-[42px] md:h-[42px]" />
<img src="/Nav item.svg" alt="Nav Item" className="w-[42px] h-[42px] " />
</button>
<button className="sidebar-icon-v3" aria-label="LLM">
<img src="/LLM.svg" alt="LLM" className="w-9 h-9 md:w-[42px] md:h-[42px]" />
<img src="/LLM.svg" alt="LLM" className="w-[42px] h-[42px] " />
</button>
<button className="sidebar-icon-v3" aria-label="FT">
<img src="/FT.svg" alt="FT" className="w-9 h-9 md:w-[42px] md:h-[42px]" />
<img src="/FT.svg" alt="FT" className="w-[42px] h-[42px] " />
</button>
<button className="sidebar-icon-v3" aria-label="Task Watcher">
<img src="/Task watacher.svg" alt="Task Watcher" className="w-9 h-9 md:w-[42px] md:h-[42px]" />
<img src="/Task watacher.svg" alt="Task Watcher" className="w-[42px] h-[42px] " />
</button>
<button className="sidebar-icon-v3" aria-label="Optimization">
<img src="/Optimization.svg" alt="Optimization" className="w-9 h-9 md:w-[42px] md:h-[42px]" />
<img src="/Optimization.svg" alt="Optimization" className="w-[42px] h-[42px] " />
</button>
</nav>
{/* Bottom Icon */}
<div className="flex flex-col gap-3 md:gap-4 mt-auto">
<div className="flex flex-col gap-4 mt-auto">
<button className="sidebar-icon-v3" aria-label="Settings">
<Settings className="w-4 h-4 md:w-5 md:h-5 text-white" />
<Settings className="w-5 h-5 text-white" />
</button>
</div>
</aside>

View File

@ -1,35 +0,0 @@
/**
* Chat Agent Component
* @description Fixed chat agent button positioned at bottom right corner
*/
// Chat icon from Figma design
const chatIcon = 'https://www.figma.com/api/mcp/asset/34915bcc-807b-45a6-82a2-2806ff0d1d6b';
/**
* ChatAgent component
* @description Floating chat agent button matching Figma design exactly
* @returns ChatAgent element
*/
export function ChatAgent() {
return (
<button
className="fixed bottom-0 right-0 flex items-center justify-center gap-2 md:gap-3 px-4 py-3 md:px-8 md:py-6 rounded-tl-xl md:rounded-tl-3xl text-white shadow-[-4px_-3px_10px_0px_rgba(123,64,129,0.25)] transition-all hover:opacity-90 z-50 min-h-[44px]"
style={{
backgroundImage:
'linear-gradient(13.793546193113684deg, rgba(0, 27, 47, 1) 10.549%, rgba(6, 38, 168, 1) 30.146%, rgba(3, 154, 152, 1) 77.418%)',
}}
aria-label="Chat Agent"
>
<img
src={chatIcon}
alt="Chat"
className="w-5 h-5 md:w-6 md:h-6 shrink-0"
/>
<span className="text-[0.875rem] md:text-[1.25rem] font-medium leading-tight whitespace-nowrap text-center hidden md:inline">
Chat Agent
</span>
</button>
);
}

View File

@ -3,7 +3,8 @@
* @description Global top header with logo, notifications, theme toggle, and profile
*/
import { Bell, ChevronDown, Moon, Sun } from 'lucide-react';
import { useState } from 'react';
import { Bell, Moon, Sun, ChevronDown } from 'lucide-react';
/**
* Header component
@ -11,37 +12,62 @@ import { Bell, ChevronDown, Moon, Sun } from 'lucide-react';
* @returns Header element
*/
export function Header() {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
return (
<header className="sticky top-0 z-[100] flex items-center justify-between px-4 py-4 md:px-6 lg:px-10 lg:pt-6 bg-gradient-to-r from-[#C8FAFF] via-[#E4F2FF] to-[#C6D7FE]">
<div className="flex items-end gap-2">
<header className="fixed top-0 mt-[24px] mb-[16px] left-0 right-0 h-16 z-[100] backdrop-blur-md flex items-center justify-between px-8">
{/* Logo */}
<div className="flex items-center">
<img
src="/Logo.png"
alt="AgenticIQ Logo"
width={160}
height={54}
className="object-contain w-24 h-auto md:w-32 lg:w-40"
width={168}
height={60}
className="object-contain"
/>
</div>
<div className="flex items-center gap-2 md:gap-3 rounded-xl md:rounded-2xl border border-[#E6EAF5] bg-white px-2 py-1.5 md:px-3 md:py-2 shadow-sm">
<button className="flex h-11 w-11 md:h-10 md:w-10 items-center justify-center rounded-full border border-[#E6EAF5] bg-white text-[#1A1A1A] transition hover:bg-[#F4F7FF] min-h-[44px] min-w-[44px]">
<Bell className="h-4 w-4 md:h-5 md:w-5" strokeWidth={1.6} />
{/* Right Actions */}
<div className="bg-white rounded-[20px] p-2 flex items-center gap-2 shadow-sm border border-gray-100/50">
{/* Bell Notification */}
<button className="p-2.5 bg-[#F4F9FF] hover:bg-[#E9F3FF] rounded-xl transition-all duration-200">
<Bell className="w-5 h-5 text-[#1A1A1A]" strokeWidth={1.5} />
</button>
<button className="flex h-11 w-11 md:h-10 md:w-10 items-center justify-center rounded-full bg-[#0A4BFF] text-white shadow-md shadow-blue-500/30 transition hover:bg-[#0033FF] min-h-[44px] min-w-[44px]">
<Sun className="h-4 w-4 md:h-5 md:w-5" strokeWidth={1.6} />
{/* Theme Toggles */}
<div className="flex items-center gap-1.5 px-1">
<button
onClick={() => setTheme('light')}
className={`p-2.5 rounded-full transition-all duration-300 ${
theme === 'light'
? 'bg-[#0033FF] text-white shadow-lg shadow-blue-500/40 scale-105'
: 'bg-[#F4F9FF] text-gray-500 hover:text-gray-700'
}`}
>
<Sun className="w-5 h-5" strokeWidth={theme === 'light' ? 2 : 1.5} />
</button>
<button className="hidden md:flex h-10 w-10 items-center justify-center rounded-full border border-[#E6EAF5] bg-white text-[#1A1A1A] transition hover:bg-[#F4F7FF]">
<Moon className="h-5 w-5" strokeWidth={1.6} />
<button
onClick={() => setTheme('dark')}
className={`p-2.5 rounded-full transition-all duration-300 ${
theme === 'dark'
? 'bg-[#0052FF] text-white shadow-lg shadow-blue-500/40 scale-105'
: 'bg-[#F4F9FF] text-gray-500 hover:text-gray-700'
}`}
>
<Moon className="w-5 h-5" strokeWidth={theme === 'dark' ? 2 : 1.5} />
</button>
<div className="flex items-center gap-1.5 md:gap-2 pl-0.5 md:pl-1 pr-1 md:pr-2">
<div className="h-9 w-9 md:h-10 md:w-10 overflow-hidden rounded-full border-2 border-white shadow-sm ring-1 ring-gray-100">
</div>
{/* Profile Section */}
<div className="flex items-center gap-2 pl-1 pr-2">
<div className="w-10 h-10 rounded-full overflow-hidden border-2 border-white shadow-sm ring-1 ring-gray-100">
<img
src="/profile.svg"
alt="User Avatar"
className="h-full w-full"
className="w-full h-full"
/>
</div>
<ChevronDown className="h-3.5 w-3.5 md:h-4 md:w-4 text-gray-900" strokeWidth={2.5} />
<ChevronDown className="w-4 h-4 text-gray-900" strokeWidth={2.5} />
</div>
</div>
</header>

View File

@ -5,5 +5,4 @@
export { RootLayout } from './root-layout';
export { Header } from './header';
export { ChatAgent } from './chat-agent';

View File

@ -6,7 +6,6 @@
import type { ReactNode } from 'react';
import { Sidebar } from '@/components/dashboard/sidebar';
import { Header } from './header';
import { ChatAgent } from './chat-agent';
interface RootLayoutProps {
children: ReactNode;
@ -23,7 +22,7 @@ export function RootLayout({ children }: RootLayoutProps) {
<div
className="min-h-screen"
style={{
backgroundColor: '#f7f8ff',
backgroundColor: '#F5F8FF',
backgroundImage: `
radial-gradient(at 0% 0%, #C8FAFF 0px, transparent 50%),
radial-gradient(at 100% 0%, #C6D7FE 0px, transparent 50%),
@ -39,12 +38,9 @@ export function RootLayout({ children }: RootLayoutProps) {
<Sidebar />
{/* Main Content Area */}
<main className="min-h-screen md:ml-[88px] lg:ml-[88px]">
<main className="ml-24 pt-16 min-h-screen">
{children}
</main>
{/* Chat Agent Button - Fixed at bottom right */}
<ChatAgent />
</div>
);
}

View File

@ -1,266 +0,0 @@
/**
* Add Tool Dialog Component
* @description Dialog for adding and configuring tools (API/MCP)
*/
import { useState, useMemo, useEffect } from 'react';
import { ChevronDown } from 'lucide-react';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogClose,
} from './dialog';
import { useClickOutside } from '@/hooks';
import type { Tool } from '@/types';
interface AddToolDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onAddTool?: (tool: Omit<Tool, 'id'>) => void;
onUpdateTool?: (toolId: string, tool: Omit<Tool, 'id'>) => void;
editingTool?: Tool | null;
}
// Tool options for the dropdown with their display names and colors
const toolOptions = [
{ name: 'Slack', displayName: 'Slack', color: '#ff0080' },
{ name: 'GitHub', displayName: 'GitHub', color: '#8fbc24' },
{ name: 'Search', displayName: 'Search', color: '#0033FF' },
{ name: 'Weather', displayName: 'Weather', color: '#03f' },
{ name: 'Calculator', displayName: 'Calculator', color: '#03f' },
{ name: 'Translator', displayName: 'Translator', color: '#03f' },
];
const DROPDOWN_MENU_CLASS =
'absolute bg-white border border-[#0033FF] border-solid flex flex-col items-start left-0 rounded-bl-[8px] rounded-br-[8px] shadow-[0px_4px_8px_0px_#EEF1F7] top-[46px] w-full z-10';
const SAVE_BUTTON_CLASS =
'bg-[#03f] flex flex-1 items-center justify-center px-[42px] py-[14px] rounded-[12px] hover:bg-[#002BCC] transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed';
const SAVE_BUTTON_TEXT_CLASS = 'font-semibold leading-[18px] not-italic text-[16px] text-center text-white';
/**
* AddToolDialog component
* @description Modal dialog for adding tools with API/MCP selection
* @param open - Whether the dialog is open
* @param onOpenChange - Callback when dialog open state changes
*/
const getInitialState = (editingTool: Tool | null | undefined) => {
if (editingTool) {
const toolOption = toolOptions.find((opt) => opt.displayName === editingTool.name);
return {
selectedType: editingTool.type as 'API' | 'MCP',
selectedTool: toolOption || null,
};
}
return {
selectedType: 'API' as const,
selectedTool: null,
};
};
export function AddToolDialog({ open, onOpenChange, onAddTool, onUpdateTool, editingTool }: AddToolDialogProps) {
const initialState = useMemo(() => getInitialState(editingTool), [editingTool]);
const [selectedType, setSelectedType] = useState<'API' | 'MCP'>(initialState.selectedType);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [selectedTool, setSelectedTool] = useState<typeof toolOptions[0] | null>(initialState.selectedTool);
// Sync state when dialog opens or editingTool changes
useEffect(() => {
if (open) {
setSelectedType(initialState.selectedType);
setSelectedTool(initialState.selectedTool);
setIsDropdownOpen(false);
} else {
setIsDropdownOpen(false);
setSelectedTool(null);
}
}, [open, initialState.selectedType, initialState.selectedTool]);
const dropdownRef = useClickOutside<HTMLDivElement>(() => setIsDropdownOpen(false), isDropdownOpen);
const handleToolSelect = (tool: typeof toolOptions[0]) => {
setSelectedTool(tool);
setIsDropdownOpen(false);
};
const handleToggleDropdown = () => {
setIsDropdownOpen((prev) => !prev);
};
const handleSave = () => {
if (!selectedTool) return;
const toolData = {
name: selectedTool.displayName,
type: selectedType,
color: selectedTool.color,
};
if (editingTool && onUpdateTool) {
onUpdateTool(editingTool.id, toolData);
} else if (onAddTool) {
onAddTool(toolData);
}
setSelectedTool(null);
setIsDropdownOpen(false);
onOpenChange(false);
};
const handleClose = () => {
setIsDropdownOpen(false);
setSelectedTool(null);
onOpenChange(false);
};
const radioButtonClass = (type: 'API' | 'MCP') =>
`relative size-5 rounded-full border-2 flex items-center justify-center transition-all ${
selectedType === type ? 'border-[#0033FF]' : 'border-[#E5E8F4]'
}`;
const dropdownButtonBaseClass =
'bg-white border border-solid flex gap-3 h-[46px] items-center justify-between px-4 py-[15px] rounded-tl-[8px] rounded-tr-[8px] shadow-[0px_4px_8px_0px_#EEF1F7] w-full cursor-pointer transition-colors';
const dropdownButtonClass = `${dropdownButtonBaseClass} ${
isDropdownOpen
? 'border-[#0033FF] shadow-[0px_4px_8px_0px_rgba(0,51,255,0.25)]'
: 'border-[#E5E8F4] hover:border-[#0033FF]'
}`;
const dropdownItemBaseClass =
'w-full px-4 py-[13px] text-left font-normal text-sm leading-[20px] transition-colors border-b border-[#E5E8F4] last:border-b-0';
const dropdownItemClass = (isSelected: boolean) =>
`${dropdownItemBaseClass} ${
isSelected
? 'bg-[rgba(0,51,255,0.1)] text-[#0033FF]'
: 'text-black hover:bg-[rgba(0,51,255,0.05)]'
}`;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-[720px]">
<DialogHeader>
<div className="flex flex-col gap-0.5">
<DialogTitle className="font-semibold text-[22px] text-black leading-normal">
{editingTool ? 'Update Tool' : 'Add Tool'}
</DialogTitle>
<DialogDescription className="font-normal text-base text-[rgba(0,0,0,0.75)]">
Quick easy smart configuration utility
</DialogDescription>
</div>
<DialogClose onClick={handleClose} />
</DialogHeader>
<div className="px-10 pt-6 pb-8">
<div className="flex flex-col gap-6">
{/* API/MCP Radio Button Selection */}
<div className="flex gap-4 items-start">
{/* API Radio Button */}
<label className="flex gap-1 items-center cursor-pointer group">
<input
type="radio"
name="toolType"
value="API"
checked={selectedType === 'API'}
onChange={() => setSelectedType('API')}
className="sr-only"
/>
<div className="relative size-5 flex items-center justify-center">
<div className={radioButtonClass('API')}>
{selectedType === 'API' && (
<div className="size-2.5 rounded-full bg-[#0033FF]" />
)}
</div>
</div>
<span className="font-medium text-sm text-black leading-[1.6] select-none">
API
</span>
</label>
{/* MCP Radio Button */}
<label className="flex gap-1 items-center cursor-pointer group">
<input
type="radio"
name="toolType"
value="MCP"
checked={selectedType === 'MCP'}
onChange={() => setSelectedType('MCP')}
className="sr-only"
/>
<div className="relative size-5 flex items-center justify-center">
<div className={radioButtonClass('MCP')}>
{selectedType === 'MCP' && (
<div className="size-2.5 rounded-full bg-[#0033FF]" />
)}
</div>
</div>
<span className="font-medium text-sm text-black leading-[1.6] select-none">
MCP
</span>
</label>
</div>
{/* Configure Tools Dropdown */}
<div className="flex flex-col gap-2.5 items-start relative w-full">
<label className="font-medium text-sm text-black leading-[1.6]">
Configure tools
</label>
<div className="relative w-full">
<div
onClick={handleToggleDropdown}
className={dropdownButtonClass}
>
<span className="flex-1 font-normal text-sm text-black text-left tracking-[0.2px]">
{selectedTool?.displayName || 'Select'}
</span>
<div className="flex items-center justify-center relative shrink-0">
<ChevronDown
className={`size-5 transition-transform ${
isDropdownOpen ? 'rotate-180' : ''
}`}
/>
</div>
</div>
{/* Dropdown Menu */}
{isDropdownOpen && (
<div
ref={dropdownRef}
className={DROPDOWN_MENU_CLASS}
>
{toolOptions.map((tool) => (
<button
key={tool.name}
type="button"
onClick={() => handleToolSelect(tool)}
className={dropdownItemClass(selectedTool?.name === tool.name)}
>
{tool.displayName}
</button>
))}
</div>
)}
</div>
</div>
{/* Save Button */}
<div className="flex h-[46px] items-center justify-center w-full">
<button
type="button"
onClick={handleSave}
disabled={!selectedTool}
className={SAVE_BUTTON_CLASS}
>
<span className={SAVE_BUTTON_TEXT_CLASS}>
{editingTool ? 'Update' : 'Save'}
</span>
</button>
</div>
</div>
</div>
</DialogContent>
</Dialog>
);
}

View File

@ -1,227 +0,0 @@
/**
* Configure Memory Dialog Component
* @description Dialog for configuring memory settings (max messages stored)
*/
import { useState, useMemo, useEffect } from 'react';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogClose,
} from './dialog';
import { useEscapeKey, useBodyScrollLock } from '@/hooks';
interface ConfigureMemoryDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
initialValue?: number;
onSave?: (value: number) => void;
}
/**
* ConfigureMemoryDialog component
* @description Modal dialog for configuring memory settings
* @param open - Whether the dialog is open
* @param onOpenChange - Callback when dialog open state changes
* @param initialValue - Initial memory value (default: 2)
* @param onSave - Callback when memory is saved
*/
const MIN_VALUE = 2;
const MAX_VALUE = 10;
const clampValue = (value: number, min: number, max: number): number => {
return Math.max(min, Math.min(max, value));
};
const calculatePercentage = (value: number, min: number, max: number): number => {
return ((value - min) / (max - min)) * 100;
};
const SLIDER_GRADIENT = 'linear-gradient(270deg, #03F 0%, #00E2E0 100%)';
const MEMORY_CARD_CLASS =
'bg-[rgba(0,0,0,0.05)] border border-[#e5e8f4] border-solid flex flex-col h-[128px] items-start p-4 relative rounded-[12px] w-full';
const MEMORY_LABEL_CLASS =
'font-medium leading-[1.6] not-italic relative shrink-0 text-[#171d25] text-sm w-full whitespace-pre-wrap';
const MIN_LABEL_WRAPPER_CLASS = 'flex flex-col items-center justify-center relative shrink-0 w-[7px]';
const MIN_LABEL_CLASS =
'font-medium leading-4 not-italic relative shrink-0 text-xs text-black w-full whitespace-pre-wrap';
const VALUE_DISPLAY_CLASS =
'bg-white border border-[rgba(0,0,0,0.1)] border-solid flex items-center justify-end px-2 py-1.5 relative rounded-[6px] shrink-0';
const VALUE_TEXT_CLASS = 'font-normal leading-[1.6] not-italic relative shrink-0 text-xs text-black';
const SAVE_BUTTON_CLASS =
'bg-[#03f] flex flex-1 items-center justify-center px-[42px] py-[14px] relative rounded-[12px] hover:bg-[#002BCC] active:bg-[#0025AA] transition-colors cursor-pointer focus:outline-none focus:ring-2 focus:ring-[#0033FF] focus:ring-offset-2';
const BUTTON_TEXT_CLASS = 'font-semibold leading-[18px] not-italic relative shrink-0 text-sm text-center text-white';
const FILLED_TRACK_CLASS = 'absolute left-0 top-0 h-full rounded-[50px] transition-all duration-200';
const SLIDER_INPUT_CLASS = 'absolute top-[-5px] left-0 w-full h-2 opacity-0 cursor-pointer z-30 appearance-none';
const SLIDER_THUMB_CLASS =
'absolute top-[-5px] size-[18px] bg-white rounded-full border-2 border-[#0033FF] shadow-md pointer-events-none transition-all duration-200 z-20';
const MAX_LABEL_CLASS = 'font-medium leading-4 not-italic relative shrink-0 text-xs text-black';
export function ConfigureMemoryDialog({
open,
onOpenChange,
initialValue = MIN_VALUE,
onSave,
}: ConfigureMemoryDialogProps) {
const clampedInitial = useMemo(
() => clampValue(initialValue, MIN_VALUE, MAX_VALUE),
[initialValue]
);
const [memoryValue, setMemoryValue] = useState(clampedInitial);
useEffect(() => {
if (open) {
setMemoryValue(clampedInitial);
}
}, [open, clampedInitial]);
useEscapeKey(() => onOpenChange(false), open);
useBodyScrollLock(open);
const handleSave = () => {
if (onSave && memoryValue >= MIN_VALUE && memoryValue <= MAX_VALUE) {
onSave(memoryValue);
}
onOpenChange(false);
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLButtonElement>) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleSave();
}
};
const handleSliderChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setMemoryValue(Number(e.target.value));
};
const percentage = useMemo(
() => calculatePercentage(memoryValue, MIN_VALUE, MAX_VALUE),
[memoryValue]
);
const sliderThumbStyle = useMemo(
() => ({
left: `clamp(0px, calc(${percentage}% - 9px), calc(100% - 18px))`,
}),
[percentage]
);
const filledTrackStyle = useMemo(
() => ({
width: `${percentage}%`,
background: SLIDER_GRADIENT,
}),
[percentage]
);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="w-[630px]">
<DialogHeader>
<div className="flex flex-col gap-0.5">
<DialogTitle className="font-semibold text-[22px] text-black leading-normal">
Configure Memory
</DialogTitle>
<DialogDescription className="font-normal text-sm text-[rgba(0,0,0,0.75)] max-w-[484px]">
Defines the maximum number of messages the agent can retain in short-term memory
</DialogDescription>
</div>
<DialogClose onClick={onOpenChange.bind(null, false)} />
</DialogHeader>
<div className="pl-[40px] pr-[32px] pt-8 pb-8">
<div className="flex flex-col gap-8 items-end">
{/* Memory Configuration Card */}
<div className={MEMORY_CARD_CLASS}>
<div className="flex flex-col gap-4 items-start overflow-clip relative shrink-0 w-full">
<p className={MEMORY_LABEL_CLASS}>
Max message stored as Short-term Memory
<span className="text-[#ef3125]">*</span>
</p>
{/* Slider Container */}
<div className="flex flex-col items-start relative shrink-0 w-full">
<div className="flex gap-1.5 items-center relative shrink-0 w-full">
{/* Min Value Label */}
<div className={MIN_LABEL_WRAPPER_CLASS}>
<p className={MIN_LABEL_CLASS}>
{MIN_VALUE}
</p>
</div>
{/* Slider Track */}
<div className="flex-1 relative h-2 bg-[rgba(0,0,0,0.1)] rounded-[50px]">
{/* Filled Track */}
<div
className={FILLED_TRACK_CLASS}
style={filledTrackStyle}
/>
{/* Slider Input (transparent overlay) */}
<input
type="range"
min={MIN_VALUE}
max={MAX_VALUE}
step="1"
value={memoryValue}
onChange={handleSliderChange}
className={SLIDER_INPUT_CLASS}
style={{
background: 'transparent',
WebkitAppearance: 'none',
MozAppearance: 'none',
}}
/>
{/* Custom Thumb */}
<div
className={SLIDER_THUMB_CLASS}
style={sliderThumbStyle}
/>
</div>
{/* Max Value Label */}
<div className="flex flex-col items-center justify-center relative shrink-0">
<p className={MAX_LABEL_CLASS}>
{MAX_VALUE}
</p>
</div>
</div>
</div>
{/* Current Value Display */}
<div className={VALUE_DISPLAY_CLASS}>
<p className={VALUE_TEXT_CLASS}>
{memoryValue} Messages
</p>
</div>
</div>
</div>
{/* Create Button */}
<div className="flex items-center justify-center relative shrink-0 w-full">
<button
type="button"
onClick={handleSave}
onKeyDown={handleKeyDown}
className={SAVE_BUTTON_CLASS}
aria-label="Save memory configuration"
>
<p className={BUTTON_TEXT_CLASS}>
Create
</p>
</button>
</div>
</div>
</div>
</DialogContent>
</Dialog>
);
}

View File

@ -1,162 +0,0 @@
/**
* Dialog component
* @description Reusable modal/dialog component with backdrop
*/
import * as React from 'react';
import { cn } from '@/lib/utils';
interface DialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
children: React.ReactNode;
className?: string;
}
interface DialogContentProps {
children: React.ReactNode;
className?: string;
}
interface DialogHeaderProps {
children: React.ReactNode;
className?: string;
}
interface DialogTitleProps {
children: React.ReactNode;
className?: string;
}
interface DialogDescriptionProps {
children: React.ReactNode;
className?: string;
}
interface DialogCloseProps {
className?: string;
onClick?: () => void;
}
/**
* Dialog component
* @description Modal dialog with backdrop blur
*/
export function Dialog({ open, onOpenChange, children, className }: DialogProps) {
if (!open) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
{/* Backdrop */}
<div
className="fixed inset-0 bg-black/35 backdrop-blur-[17.5px]"
onClick={() => onOpenChange(false)}
aria-hidden="true"
/>
{/* Dialog Content */}
<div className={cn('relative z-50', className)}>{children}</div>
</div>
);
}
/**
* DialogContent component
* @description Container for dialog content
*/
export function DialogContent({ children, className }: DialogContentProps) {
return (
<div
className={cn(
'bg-white rounded-[24px] shadow-lg w-full max-w-[720px]',
className
)}
onClick={(e) => e.stopPropagation()}
>
{children}
</div>
);
}
/**
* DialogHeader component
* @description Header section of dialog
*/
export function DialogHeader({ children, className }: DialogHeaderProps) {
return (
<div
className={cn(
'bg-[#EEF1F7] border-b border-[#E5E8F4] px-8 py-6 rounded-tl-[24px] rounded-tr-[24px] flex items-center justify-between',
className
)}
>
{children}
</div>
);
}
/**
* DialogTitle component
* @description Title section of dialog header
*/
export function DialogTitle({ children, className }: DialogTitleProps) {
return (
<div className={cn('flex flex-col gap-0.5', className)}>{children}</div>
);
}
/**
* DialogDescription component
* @description Description/subtitle in dialog header
*/
export function DialogDescription({
children,
className,
}: DialogDescriptionProps) {
return (
<p
className={cn(
'font-["Poppins:Regular",sans-serif] text-base text-[rgba(0,0,0,0.75)]',
className
)}
>
{children}
</p>
);
}
/**
* DialogClose component
* @description Close button for dialog
*/
export function DialogClose({ className, onClick }: DialogCloseProps) {
const closeIcon = 'https://www.figma.com/api/mcp/asset/d151f987-7858-49a2-85dd-eac10b6bf0b0';
const closeCircleBg = 'https://www.figma.com/api/mcp/asset/3702e7bd-24a9-4380-83be-0347e18baa40';
return (
<button
type="button"
onClick={onClick}
className={cn(
'relative size-[30px] flex items-center justify-center cursor-pointer',
className
)}
aria-label="Close dialog"
>
<div className="absolute inset-0">
<img
src={closeCircleBg}
alt=""
className="block max-w-none size-full"
/>
</div>
<div className="relative ml-[3px] mt-[3.5px] size-6">
<img
src={closeIcon}
alt=""
className="block max-w-none size-full"
/>
</div>
</button>
);
}

View File

@ -1,61 +0,0 @@
/**
* Tool Chip Component
* @description Displays a tool chip with name, close button, and redirect button
*/
import { X, ArrowRight } from 'lucide-react';
import type { Tool } from '@/types';
interface ToolChipProps {
tool: Tool;
onDelete: (id: string) => void;
onRedirect: (id: string) => void;
}
/**
* ToolChip component
* @description Chip component for displaying tools with delete and redirect actions
* Matches Figma design with exact styling
* @param tool - The tool object containing id, name, and color
* @param onDelete - Callback function called when delete button is clicked, receives tool id
* @param onRedirect - Callback function called when redirect button is clicked, receives tool id
* @returns {JSX.Element} A chip component displaying the tool with action buttons
*/
export function ToolChip({ tool, onDelete, onRedirect }: ToolChipProps): JSX.Element {
return (
<div className="bg-white border border-[#eef1f7] border-solid flex gap-8 h-[32px] items-center px-3 py-[5px] relative rounded-[6px] shrink-0">
<div className="flex items-center relative shrink-0">
<p
className="font-normal leading-[1.5] not-italic relative shrink-0 text-[14px]"
style={{ color: tool.color }}
>
{tool.name}
</p>
</div>
<div className="flex gap-1 items-center justify-end leading-[0] relative shrink-0">
{/* Close Button - matches Figma design */}
<button
type="button"
onClick={() => onDelete(tool.id)}
className="relative size-[18px] flex items-center justify-center cursor-pointer hover:opacity-80 transition-opacity group"
aria-label={`Delete ${tool.name}`}
>
<div className="absolute inset-0 size-[18px] rounded-full bg-[rgba(0,0,0,0.08)] group-hover:bg-[rgba(0,0,0,0.12)] transition-colors" />
<X className="absolute size-[14.4px] text-black ml-[1.8px] mt-[2.1px]" strokeWidth={2} />
</button>
{/* Redirect Button - matches Figma design */}
<button
type="button"
onClick={() => onRedirect(tool.id)}
className="relative size-[16px] flex items-center justify-center cursor-pointer hover:opacity-80 transition-opacity group"
aria-label={`Configure ${tool.name}`}
>
<div className="absolute inset-0 size-[16px] rounded-full bg-[rgba(0,0,0,0.08)] group-hover:bg-[rgba(0,0,0,0.12)] transition-colors" />
<ArrowRight className="absolute size-[12.8px] text-black ml-[1.78px] mt-[1.78px]" strokeWidth={2} />
</button>
</div>
</div>
);
}

View File

@ -1,95 +0,0 @@
/**
* Agents Data Constants
* @description Mock data for agents list
*/
/**
* Agent data interface
*/
export interface AgentData {
title: string;
tags: string[];
description: string;
}
/**
* Mock agents data
*/
export const MOCK_AGENTS: AgentData[] = [
{
title: 'Customer Support Agent',
tags: ['GPT-4', 'Chat Interface'],
description: 'Automates customer service inquiries with intelligent escalation and context preservation.',
},
{
title: 'Document Analyst',
tags: ['Claude-3', 'PDF Processing', 'Knowledge Graph'],
description: 'Analyzes documents to extract insights and generate comprehensive reports automatically.',
},
{
title: 'Sales Qualifier',
tags: ['Lead Scoring', 'CRM Integration', 'Email Autom...'],
description: 'Qualifies prospects, schedules meetings, and manages follow-up communications with tracking.',
},
{
title: 'Customer Support Agent',
tags: ['GPT-4', 'Chat Interface'],
description: 'Automates customer service inquiries with intelligent escalation and context preservation.',
},
{
title: 'Document Analyst',
tags: ['Claude-3', 'PDF Processing', 'Knowledge Graph'],
description: 'Analyzes documents to extract insights and generate comprehensive reports automatically.',
},
{
title: 'Sales Qualifier',
tags: ['Lead Scoring', 'CRM Integration', 'Email Autom...'],
description: 'Qualifies prospects, schedules meetings, and manages follow-up communications with tracking.',
},
{
title: 'Data Analyst Agent',
tags: ['GPT-4', 'Data Processing'],
description: 'Processes and analyzes large datasets to generate insights and visualizations.',
},
{
title: 'Content Writer',
tags: ['Claude-3', 'Content Generation'],
description: 'Generates high-quality content for blogs, articles, and marketing materials.',
},
{
title: 'Code Reviewer',
tags: ['GPT-4', 'Code Analysis'],
description: 'Reviews code for quality, security, and best practices automatically.',
},
{
title: 'Email Assistant',
tags: ['GPT-4', 'Email Management'],
description: 'Manages and responds to emails intelligently with context awareness.',
},
{
title: 'Meeting Scheduler',
tags: ['Calendar Integration', 'AI Scheduling'],
description: 'Schedules meetings by understanding natural language requests and availability.',
},
{
title: 'Research Assistant',
tags: ['Claude-3', 'Web Research'],
description: 'Conducts comprehensive research on topics and compiles detailed reports.',
},
{
title: 'Translation Agent',
tags: ['Multi-language', 'GPT-4'],
description: 'Translates content between multiple languages with high accuracy.',
},
{
title: 'Quality Assurance',
tags: ['Testing', 'Automation'],
description: 'Automates quality assurance processes and generates test reports.',
},
{
title: 'Financial Analyst',
tags: ['Data Analysis', 'Reporting'],
description: 'Analyzes financial data and generates comprehensive reports and insights.',
},
];

View File

@ -3,7 +3,7 @@
* @description Reusable hooks following AgenticIQ best practices
*/
import { useState, useEffect, useRef, type RefObject } from 'react'
import { useState, useEffect } from 'react'
/**
* Hook for managing media queries
@ -56,78 +56,3 @@ export function useDebounce<T>(value: T, delay: number): T {
return debouncedValue
}
/**
* Hook for handling click outside events
* @description Closes dropdown/modal when clicking outside the element
* @param handler - Callback function to execute when click outside occurs
* @param enabled - Whether the hook is enabled
* @returns Ref to attach to the element
* @example
* const ref = useClickOutside(() => setIsOpen(false), isOpen)
*/
export function useClickOutside<T extends HTMLElement>(
handler: () => void,
enabled: boolean = true
): RefObject<T> {
const ref = useRef<T>(null)
useEffect(() => {
if (!enabled) return
const handleClick = (event: MouseEvent) => {
if (ref.current && !ref.current.contains(event.target as Node)) {
handler()
}
}
document.addEventListener('mousedown', handleClick)
return () => document.removeEventListener('mousedown', handleClick)
}, [handler, enabled])
return ref
}
/**
* Hook for handling escape key press
* @description Executes callback when Escape key is pressed
* @param handler - Callback function to execute
* @param enabled - Whether the hook is enabled
* @example
* useEscapeKey(() => onClose(), isOpen)
*/
export function useEscapeKey(handler: () => void, enabled: boolean = true): void {
useEffect(() => {
if (!enabled) return
const handleEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
handler()
}
}
document.addEventListener('keydown', handleEscape)
return () => document.removeEventListener('keydown', handleEscape)
}, [handler, enabled])
}
/**
* Hook for locking/unlocking body scroll
* @description Prevents body scroll when modal/dialog is open
* @param locked - Whether body scroll should be locked
* @example
* useBodyScrollLock(isModalOpen)
*/
export function useBodyScrollLock(locked: boolean): void {
useEffect(() => {
if (locked) {
document.body.style.overflow = 'hidden'
} else {
document.body.style.overflow = 'unset'
}
return () => {
document.body.style.overflow = 'unset'
}
}, [locked])
}

View File

@ -3,7 +3,7 @@
@layer base {
body {
font-family: 'Poppins', sans-serif;
font-size: 0.875rem; /* 14px - Base body text size */
font-size: 12px;
}
:root {
@ -59,22 +59,6 @@
}
.sidebar-icon-v3 {
@apply w-9 h-9 md:w-[42px] md:h-[42px] flex items-center justify-center text-white hover:bg-white/10 rounded-xl transition-all duration-300 min-h-[44px] min-w-[44px] md:min-h-[42px] md:min-w-[42px];
}
/* Global Font Utilities */
.font-regular {
font-family: 'Poppins', sans-serif;
font-weight: 400;
}
.font-medium {
font-family: 'Poppins', sans-serif;
font-weight: 500;
}
.font-semibold {
font-family: 'Poppins', sans-serif;
font-weight: 600;
@apply w-[42px] h-[42px] flex items-center justify-center text-white hover:bg-white/10 rounded-xl transition-all duration-300;
}
}

View File

@ -1,354 +0,0 @@
import { useState } from 'react';
import { Link } from '@tanstack/react-router';
import { Info, ChevronDown } from 'lucide-react';
import { APP_PATHS } from '@/routes';
import { AddToolDialog } from '@/components/ui/add-tool-dialog';
import { ConfigureMemoryDialog } from '@/components/ui/configure-memory-dialog';
import { ToolChip } from '@/components/ui/tool-chip';
import type { Tool } from '@/types';
/**
* Toggle Switch Component
* @description A toggle switch that visually represents on/off state
*/
function ToggleSwitch({ enabled }: { enabled: boolean }) {
return (
<div
className={`relative h-5 w-[35px] rounded-full transition-colors duration-200 ${
enabled ? 'bg-[#0033FF]' : 'bg-gray-300'
}`}
>
<div
className={`absolute top-0.5 h-4 w-4 rounded-full bg-white shadow-sm transition-transform duration-200 ${
enabled ? 'translate-x-[15px]' : 'translate-x-0.5'
}`}
/>
</div>
);
}
/**
* AgentCreatePage component
* @description Page for creating a new agent with configuration options
* @returns Agent creation form page
*/
export function AgentCreatePage() {
const [enableKb, setEnableKb] = useState(false);
const [enableMemory, setEnableMemory] = useState(true);
const [isToolDialogOpen, setIsToolDialogOpen] = useState(false);
const [isMemoryDialogOpen, setIsMemoryDialogOpen] = useState(false);
const [memoryValue, setMemoryValue] = useState(2);
const [editingTool, setEditingTool] = useState<Tool | null>(null);
const [tools, setTools] = useState<Tool[]>([
{ id: '1', name: 'Slack', type: 'API', color: '#ff0080' },
{ id: '2', name: 'GitHub', type: 'MCP', color: '#8fbc24' },
]);
const handleAddTool = (toolData: Omit<Tool, 'id'>) => {
const newTool: Tool = {
...toolData,
id: Date.now().toString(), // Simple ID generation
};
setTools((prev) => [...prev, newTool]);
};
const handleUpdateTool = (toolId: string, toolData: Omit<Tool, 'id'>) => {
setTools((prev) =>
prev.map((tool) => (tool.id === toolId ? { ...tool, ...toolData } : tool))
);
setEditingTool(null);
};
const handleDeleteTool = (id: string) => {
setTools((prev) => prev.filter((tool) => tool.id !== id));
};
const handleRedirectTool = (id: string) => {
// Find the tool and open configuration/update dialog
const tool = tools.find((t) => t.id === id);
if (tool) {
setEditingTool(tool);
setIsToolDialogOpen(true);
}
};
const handleDialogClose = (open: boolean) => {
setIsToolDialogOpen(open);
if (!open) {
setEditingTool(null);
}
};
return (
<div className="min-h-screen px-[50px] py-6">
<div className="mb-6">
<h1 className="font-semibold text-[20px] text-black leading-normal">
Add Agent
</h1>
</div>
<div className="flex flex-col gap-6">
{/* Main Form Card */}
<div className="bg-white border border-[rgba(0,0,0,0.1)] rounded-[24px] p-8 shadow-md">
<div className="grid gap-6 lg:grid-cols-2">
{/* Left Column */}
<div className="flex flex-col gap-6">
<div className="flex flex-col gap-1.5">
<label className="font-medium text-sm text-black leading-[1.6]">
Name<span className="text-[#ef3125]">*</span>
</label>
<input
className="w-full h-[46px] rounded-lg border border-[#E5E8F4] bg-white px-4 py-3 text-sm text-[rgba(0,0,0,0.75)] shadow-[0px_2px_4px_0px_#EEF1F7] outline-none focus:border-[#0033FF] tracking-[0.2px]"
placeholder="Input here"
type="text"
/>
</div>
<div className="flex flex-col gap-1.5">
<label className="font-medium text-sm text-black leading-[1.6]">
Agent Description
<span className="text-[#ef3125]">*</span>
</label>
<textarea
rows={4}
className="w-full h-[135px] rounded-lg border border-[#E5E8F4] bg-white px-4 py-3 text-sm text-[rgba(0,0,0,0.75)] shadow-[0px_2px_4px_0px_#EEF1F7] outline-none focus:border-[#0033FF] tracking-[0.2px] resize-none"
placeholder="Lorem Ipsum is simply dummy text"
name="agentDescription"
/>
</div>
<div className="grid gap-8 md:grid-cols-2">
<div className="flex flex-col gap-1.5">
<label className="font-medium text-sm text-black leading-[1.6]">
LLM Provider
<span className="text-[#ef3125]">*</span>
</label>
<div className="flex items-center justify-between h-[46px] rounded-lg border border-[#E5E8F4] bg-white px-4 py-3 shadow-[0px_2px_4px_0px_#EEF1F7]">
<span className="text-sm text-[rgba(0,0,0,0.75)] tracking-[0.2px]">
Select
</span>
<ChevronDown className="shrink-0 size-5 text-[rgba(0,0,0,0.75)]" />
</div>
</div>
<div className="flex flex-col gap-1.5">
<label className="font-medium text-sm text-black leading-[1.6]">
LLM Model
<span className="text-[#ef3125]">*</span>
</label>
<div className="flex items-center justify-between h-[46px] rounded-lg border border-[#E5E8F4] bg-white px-4 py-3 shadow-[0px_2px_4px_0px_#EEF1F7]">
<span className="text-sm text-[rgba(0,0,0,0.75)] tracking-[0.2px]">
Select
</span>
<ChevronDown className="shrink-0 size-5 text-[rgba(0,0,0,0.75)]" />
</div>
</div>
</div>
</div>
{/* Right Column */}
<div className="flex flex-col gap-6 border border-[rgba(0,0,0,0.15)] rounded-[24px] p-8">
<div className="flex flex-col gap-1.5">
<label className="font-medium text-sm text-black leading-[1.6]">
Agent Role<span className="text-[#ef3125]">*</span>
</label>
<input
className="w-full h-[46px] rounded-lg border border-[#E5E8F4] bg-white px-4 py-3 text-sm text-[rgba(0,0,0,0.75)] shadow-[0px_2px_4px_0px_#EEF1F7] outline-none focus:border-[#0033FF] tracking-[0.2px]"
placeholder="Input here"
type="text"
/>
</div>
<div className="flex flex-col gap-1.5">
<label className="font-medium text-sm text-black leading-[1.6]">
Prompt<span className="text-[#ef3125]">*</span>
</label>
<input
className="w-full h-[46px] rounded-lg border border-[#E5E8F4] bg-white px-4 py-3 text-sm text-[rgba(0,0,0,0.75)] shadow-[0px_2px_4px_0px_#EEF1F7] outline-none focus:border-[#0033FF] tracking-[0.2px]"
placeholder="Your Prompt"
/>
</div>
<div className="flex flex-col gap-3">
<div className="flex items-center justify-between">
<label className="font-medium text-sm text-black leading-[1.6]">
Instruction & Suggestion
<span className="text-[#ef3125]">*</span>
</label>
<button
className="bg-[rgba(0,51,255,0.06)] border border-[#333] rounded-md px-3 py-1.5 text-xs font-medium text-[#333] hover:bg-[rgba(0,51,255,0.1)] transition-colors"
>
Improve Instruction
</button>
</div>
<textarea
rows={3}
className="w-full h-[78px] rounded-lg border border-[#E5E8F4] bg-white px-4 py-3 text-sm text-[rgba(0,0,0,0.75)] shadow-[0px_2px_4px_0px_#EEF1F7] outline-none focus:border-[#0033FF] tracking-[0.2px] resize-none"
placeholder="Lorem Ipsum is simply dummy text"
/>
</div>
</div>
</div>
</div>
{/* Features Card */}
<div className="bg-white border border-[rgba(0,0,0,0.1)] rounded-[24px] p-8 shadow-md">
<div className="flex flex-col gap-6">
<h2 className="font-semibold text-base text-black leading-[1.6]">
Features
</h2>
<div className="grid gap-6 md:grid-cols-3">
{/* Knowledge Base */}
<div className="flex flex-col gap-2">
<div
className={`flex items-center justify-between h-[56px] rounded-lg px-4 py-[13px] transition-all ${
enableKb
? 'border border-[rgba(0,51,255,0.25)] shadow-[0px_2px_12px_0px_rgba(0,51,255,0.5)]'
: 'border border-[#E5E8F4] bg-white shadow-[0px_2px_4px_0px_#EEF1F7]'
}`}
>
<div className="flex items-center gap-0.5">
<span className="font-medium text-sm text-black leading-[1.5]">
Knowledge Base
</span>
<Info className="size-4 text-black" />
</div>
<button
onClick={() => setEnableKb((v) => !v)}
className="relative shrink-0"
aria-pressed={enableKb}
>
<ToggleSwitch enabled={enableKb} />
</button>
</div>
{enableKb && (
<div className="flex items-center justify-between">
<button className="flex items-center gap-0.5 text-xs font-medium text-[#03f] leading-[1.5] hover:underline">
Configure
<img src="/Agent/redirect.svg" alt="Redirect" className="size-3.5 text-[#03f]" />
</button>
<div className="bg-white h-[22px] rounded px-1.5 py-0.5" />
</div>
)}
</div>
{/* Memory */}
<div className="flex flex-col gap-2">
<div
className={`flex items-center justify-between h-[56px] rounded-lg px-4 py-[13px] transition-all ${
enableMemory
? 'border border-[rgba(0,51,255,0.25)] shadow-[0px_2px_12px_0px_rgba(0,51,255,0.5)]'
: 'border border-[#E5E8F4] bg-white shadow-[0px_2px_4px_0px_#EEF1F7]'
}`}
>
<div className="flex items-center gap-0.5">
<span className="font-medium text-sm text-black leading-[1.5]">
Memory
</span>
<Info className="size-4 text-black" />
</div>
<button
onClick={() => setEnableMemory((v) => !v)}
className="relative shrink-0"
aria-pressed={enableMemory}
>
<ToggleSwitch enabled={enableMemory} />
</button>
</div>
{enableMemory && (
<div className="flex items-center justify-between">
<button
onClick={() => setIsMemoryDialogOpen(true)}
className="flex items-center gap-0.5 text-xs font-medium text-[#03f] leading-[1.5] hover:underline"
>
Configure
<img src="/Agent/redirect.svg" alt="Redirect" className="size-3.5 text-[#03f]" />
</button>
<span className="bg-white border border-[#e5e8f4] border-solid flex items-center px-1.5 py-0.5 rounded text-xs font-normal text-black text-center">
{memoryValue} Messages
</span>
</div>
)}
</div>
{/* Tool Configuration */}
<div className="flex items-center justify-between h-[56px] rounded-lg border border-[#E5E8F4] bg-white px-4 py-[13px] shadow-[0px_2px_8px_0px_#EEF1F7]">
<div className="flex flex-1 gap-0.5 items-center">
<span className="font-medium text-sm text-black leading-[1.6]">
Tool Configuration
</span>
<Info className="size-4 text-black" />
</div>
<button
onClick={() => {
setEditingTool(null);
setIsToolDialogOpen(true);
}}
className="bg-white border border-black border-solid flex items-center justify-center gap-1.5 px-3 py-[3px] rounded-[6px] hover:bg-gray-50 transition-colors"
>
<div className="relative size-6 flex items-center justify-center">
<img
src="/Agent/add.svg"
alt="Add"
className="size-3.5 filter brightness-0"
/> </div>
<span className="font-medium text-sm text-black leading-[1.5] text-left">
Add
</span>
</button>
</div>
</div>
{/* Divider */}
<div className="h-px bg-[#E5E8F4] w-full" />
{/* Tools Display Section */}
<div className="bg-[rgba(229,232,244,0.25)] border border-[#e5e8f4] border-solid flex flex-col items-start justify-center p-6 relative rounded-[12px] shrink-0 w-full">
<div className="flex gap-3 items-start relative shrink-0 flex-wrap">
{tools.map((tool) => (
<ToolChip
key={tool.id}
tool={tool}
onDelete={handleDeleteTool}
onRedirect={handleRedirectTool}
/>
))}
</div>
</div>
</div>
</div>
{/* Action Buttons */}
<div className="flex gap-2 justify-center font-semibold">
<Link
to={APP_PATHS.agent}
className="bg-white border border-[rgba(0,0,0,0.5)] rounded-xl px-[42px] py-[19px] w-[182px] flex items-center justify-center hover:bg-gray-50 transition-colors"
>
<span className="font-medium text-sm text-black text-center leading-[18px]">
Cancel
</span>
</Link>
<button
className="bg-[#03f] border border-[#03f] rounded-xl px-[42px] py-[19px] w-[182px] flex items-center justify-center hover:bg-[#002BCC] transition-colors"
>
<span className="font-medium text-sm text-white text-center leading-[18px]">
Create
</span>
</button>
</div>
</div>
{/* Add Tool Dialog */}
<AddToolDialog
open={isToolDialogOpen}
onOpenChange={handleDialogClose}
onAddTool={handleAddTool}
onUpdateTool={handleUpdateTool}
editingTool={editingTool}
/>
{/* Configure Memory Dialog */}
<ConfigureMemoryDialog
open={isMemoryDialogOpen}
onOpenChange={setIsMemoryDialogOpen}
initialValue={memoryValue}
onSave={setMemoryValue}
/>
</div>
);
}

View File

@ -1,62 +0,0 @@
import { Link } from '@tanstack/react-router';
import { APP_PATHS } from '@/routes';
const searchIcon = 'https://www.figma.com/api/mcp/asset/379327bd-47f2-4ebc-a8e0-c0004928d27b';
const emptyStateIcon = '/Agent/agent.svg';
const addIcon = '/Agent/add.svg';
export function AgentPage() {
return (
<div className="px-4 py-6 md:px-6 md:py-8 lg:px-10 lg:pt-10 lg:pb-16">
<div className="flex flex-col gap-6 md:gap-8">
<div className="space-y-2">
<h1 className="text-[1.75rem] md:text-[2rem] lg:text-[2.5rem] font-semibold text-[#1B1B1B] leading-tight">
Agent
</h1>
<p className="max-w-3xl text-[0.875rem] md:text-[0.9375rem] lg:text-[1rem] text-[#444750]">
Intelligent Agents that automate workflows and enhance decision-making
</p>
</div>
<div className="flex items-center justify-between">
<div className="flex-1 w-full max-w-2xl">
<div className="flex items-center gap-3 rounded-xl border border-[#E1E4EF] bg-white px-3 py-2.5 md:px-4 md:py-3 shadow-sm">
<img src={searchIcon} alt="Search" className="h-4 w-4 md:h-5 md:w-5 opacity-70" />
<input
type="text"
placeholder="Search Agent"
className="w-full border-none bg-transparent text-[0.875rem] md:text-[0.9375rem] lg:text-[1rem] text-[#6F7282] placeholder:text-[#9DA3B0] focus:outline-none"
/>
</div>
</div>
</div>
<div className="flex min-h-[400px] md:min-h-[540px] items-center justify-center">
<div className="flex flex-col items-center gap-4 md:gap-6 text-center px-4">
<img
src={emptyStateIcon}
alt="No agent"
className="h-24 w-24 md:h-32 md:w-32 lg:h-36 lg:w-36"
/>
<div className="space-y-2">
<h2 className="text-[1.5rem] md:text-[1.75rem] lg:text-[2rem] font-semibold text-[#252526]">
No Agent Found
</h2>
<p className="text-[0.75rem] md:text-[0.8125rem] lg:text-[0.875rem] text-[#6D6B70]">
Get started by adding your first Agent
</p>
</div>
<Link
to={APP_PATHS.agentCreate}
className="inline-flex items-center gap-2 rounded-xl bg-[#0033FF] px-4 py-2.5 md:px-5 md:py-3 text-[0.875rem] md:text-[0.9375rem] lg:text-[1rem] font-semibold text-white shadow-lg shadow-blue-500/25 transition hover:bg-[#002BCC] min-h-[44px]"
>
<img src={addIcon} alt="Add" className="h-4 w-4 md:h-5 md:w-5" />
Add Agent
</Link>
</div>
</div>
</div>
</div>
);
}

View File

@ -3,11 +3,11 @@
* @description Main dashboard page with metrics and agent cards
*/
import { useState } from 'react';
import { MetricCard } from '@/components/dashboard/metric-card';
import { AgentsSection } from '@/components/dashboard/agents-section';
import { AgentCard } from '@/components/dashboard/agent-card';
import { Button } from '@/components/ui/button';
import { Search, Plus } from 'lucide-react';
import { MOCK_AGENTS } from '@/constants/agents';
/**
* Dashboard component
@ -15,72 +15,135 @@ import { MOCK_AGENTS } from '@/constants/agents';
* @returns Dashboard element
*/
export function Dashboard() {
const [activeTab, setActiveTab] = useState<'all' | 'my'>('all');
const agents = [
{
title: 'Customer Support Agent',
tags: ['GPT-4', 'Chat Interface'],
description: 'Automates customer service inquiries with intelligent escalation and context preservation.',
},
{
title: 'Document Analyst',
tags: ['Claude-3', 'PDF Processing', 'Knowledge Graph'],
description: 'Analyzes documents to extract insights and generate comprehensive reports automatically.',
},
{
title: 'Sales Qualifier',
tags: ['Lead Scoring', 'CRM Integration', 'Email Autom...'],
description: 'Qualifies prospects, schedules meetings, and manages follow-up communications with tracking.',
},
{
title: 'Customer Support Agent',
tags: ['GPT-4', 'Chat Interface'],
description: 'Automates customer service inquiries with intelligent escalation and context preservation.',
},
{
title: 'Document Analyst',
tags: ['Claude-3', 'PDF Processing', 'Knowledge Graph'],
description: 'Analyzes documents to extract insights and generate comprehensive reports automatically.',
},
{
title: 'Sales Qualifier',
tags: ['Lead Scoring', 'CRM Integration', 'Email Autom...'],
description: 'Qualifies prospects, schedules meetings, and manages follow-up communications with tracking.',
},
];
return (
<div className="px-4 py-4 md:px-6 lg:px-10 lg:pt-6 lg:pb-10">
<div className="px-8 pt-8">
{/* Content Header: Welcome + Search/Create */}
<div className="mb-4 md:mb-6 flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div className="space-y-2">
<h1 className="text-[1.75rem] md:text-[2rem] lg:text-[2.5rem] font-semibold text-[#1A1A1A] leading-tight">
Welcome, Vijay! 👋🏻
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-8">
<div>
<h1 className="text-[22px] font-semibold text-gray-900 mb-2">
Welcome, Vijay! 👋
</h1>
<p className="max-w-2xl text-[0.875rem] md:text-[0.9375rem] lg:text-[1rem] text-[#5B6170]">
Manage Agents, Workflows, Tools and Knowledge Base
<p className="text-gray-500 max-w-2xl text-[16px]">
Monitor and manage your intelligent agents, knowledge bases, and automated workflows from a centralized dashboard
</p>
</div>
<div className="flex flex-col md:flex-row items-stretch md:items-center gap-3 w-full md:w-auto">
<div className="relative flex-1 md:flex-initial">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-[#9AA1B5]" />
<div className="flex items-center gap-3">
<div className="relative group">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 group-focus-within:text-blue-500 transition-colors" />
<input
type="text"
placeholder="Search"
className="h-11 w-full md:w-[300px] lg:w-[340px] rounded-lg border border-[#E1E4EF] bg-white pl-10 pr-4 text-[0.875rem] md:text-[0.9375rem] lg:text-[1rem] text-[#4A4F5C] outline-none transition focus:border-[#0033FF] focus:ring-2 focus:ring-[#0033FF]/15 min-h-[44px]"
className="pl-10 pr-4 py-2.5 bg-white border border-gray-200 rounded-xl text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 w-64 shadow-sm transition-all"
/>
</div>
<Button className="flex h-11 items-center justify-center gap-2 rounded-lg border border-[#0033FF] bg-white px-4 md:px-5 text-[0.875rem] md:text-[0.9375rem] lg:text-[1rem] font-medium text-[#0033FF] shadow-[0_0_2px_rgba(0,51,255,0.25),0_3px_8px_rgba(0,51,255,0.12)] transition hover:bg-[#F5F8FF] min-h-[44px]">
<Plus className="h-4 w-4" />
<Button className="bg-[#0033FF] hover:bg-[#0042CC] text-white px-6 py-2.5 rounded-xl font-semibold flex items-center gap-2 shadow-lg shadow-blue-500/20 active:scale-95 transition-all h-auto">
<Plus className="w-5 h-5" />
Create Agent
</Button>
</div>
</div>
{/* Metrics Grid */}
<div className="mb-4 md:mb-7 h-full bg-[rgba(247,248,255,0.82)] border border-solid border-white content-stretch flex flex-col items-start justify-center p-4 md:p-6 relative rounded-xl md:rounded-2xl lg:rounded-3xl">
<div className="content-stretch grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 md:gap-6 items-stretch relative shrink-0 w-full">
<div className="bg-[#F3F8FF]/80 backdrop-blur-sm rounded-[32px] p-6 mb-8 border border-white/40 shadow-sm">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<MetricCard
title="ACTIVE AGENTS"
subtitle="Agents"
value="03"
gradient="blue-teal"
imageUrl="/Dashboard/Card1.svg"
/>
<MetricCard
title="KNOWLEDGE BASES"
subtitle="Knowledge Bases"
value="02"
gradient="pink-purple"
imageUrl="/Dashboard/Card2.svg"
/>
<MetricCard
title="ACTIVE WORKFLOWS"
subtitle="Workflows"
value="01"
gradient="orange-yellow"
imageUrl="/Dashboard/Card3.svg"
/>
<MetricCard
title="CONNECTED TOOLS"
subtitle="Tools"
value="01"
gradient="blue-purple"
imageUrl="/Dashboard/Card4.svg"
/>
</div>
</div>
{/* Agents Section */}
<div className="mt-4 md:mt-6">
<AgentsSection agents={MOCK_AGENTS} />
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 p-6">
{/* Tabs */}
<div className="flex gap-4 mb-6">
<button
onClick={() => setActiveTab('all')}
className={`px-6 py-2 rounded-lg text font-medium transition-colors ${activeTab === 'all'
? 'bg-black text-white'
: 'bg-white text-gray-600 border border-gray-300 hover:bg-gray-50'
}`}
>
All Agents
</button>
<button
onClick={() => setActiveTab('my')}
className={`px-6 py-2 rounded-lg font-medium transition-colors ${activeTab === 'my'
? 'bg-black text-white'
: 'bg-white text-gray-600 border border-gray-300 hover:bg-gray-50'
}`}
>
My Agents
</button>
</div>
{/* Agent Cards Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{agents.map((agent, index) => (
<AgentCard
key={index}
title={agent.title}
tags={agent.tags}
description={agent.description}
/>
))}
</div>
</div>
</div>
);

View File

@ -5,20 +5,10 @@
import { createRootRoute, createRoute, createRouter, Outlet } from '@tanstack/react-router';
import { RootLayout } from '@/components/layout';
import { AgentPage } from '@/pages/agent';
import { AgentCreatePage } from '@/pages/agent-create';
import { Dashboard } from '@/pages/dashboard';
import { SignInPage } from '@/pages/sign-in';
import { SignUpPage } from '@/pages/sign-up';
export const APP_PATHS = {
signIn: '/',
signUp: '/signup',
dashboard: '/dashboard',
agent: '/agent',
agentCreate: '/agent/create',
} as const;
const rootRoute = createRootRoute({
component: () => <Outlet />,
});
@ -41,51 +31,25 @@ const appRoute = createRoute({
const signInRoute = createRoute({
getParentRoute: () => publicRoute,
path: APP_PATHS.signIn,
path: '/',
component: SignInPage,
});
const signUpRoute = createRoute({
getParentRoute: () => publicRoute,
path: APP_PATHS.signUp,
path: '/signup',
component: SignUpPage,
});
const dashboardRoute = createRoute({
getParentRoute: () => appRoute,
path: APP_PATHS.dashboard,
path: '/dashboard',
component: Dashboard,
});
const agentRoute = createRoute({
getParentRoute: () => appRoute,
path: APP_PATHS.agent,
component: AgentPage,
});
const agentCreateRoute = createRoute({
getParentRoute: () => appRoute,
path: APP_PATHS.agentCreate,
component: AgentCreatePage,
});
const notFoundRoute = createRoute({
getParentRoute: () => rootRoute,
path: '*',
component: () => (
<div className="px-8 pt-8">
<div className="rounded-2xl border border-gray-200 bg-white/90 p-8 shadow-sm">
<h1 className="text-xl font-semibold text-gray-900">Page not found</h1>
<p className="mt-2 text-sm text-gray-600">The page you requested does not exist.</p>
</div>
</div>
),
});
const routeTree = rootRoute.addChildren([
publicRoute.addChildren([signInRoute, signUpRoute]),
appRoute.addChildren([dashboardRoute, agentRoute, agentCreateRoute]),
notFoundRoute,
appRoute.addChildren([dashboardRoute]),
]);
export const router = createRouter({

View File

@ -25,10 +25,3 @@ export interface ApiError {
}
export type LoadingState = 'idle' | 'loading' | 'success' | 'error'
export interface Tool {
id: string
name: string
type: 'API' | 'MCP'
color: string // Color for the tool name (e.g., '#ff0080' for Slack, '#8fbc24' for GitHub)
}