mirror of
https://github.com/thecyberlearn/quantum-ai-v3.git
synced 2026-08-18 22:52:58 +00:00
Refactor: Complete architecture reorganization with proper separation of concerns
BREAKING CHANGES: - Move marketplace and agent discovery views from core to agent_base app - Transfer all wallet functionality from core to dedicated wallet app - Move Stripe webhook handling to wallet app for better organization - Consolidate payment system logic under single responsibility NEW STRUCTURE: - core app: Platform pages only (homepage, pricing) - agent_base app: Complete agent marketplace and catalog system - wallet app: Full payment system with Stripe integration - Individual agent apps: Unchanged, self-contained IMPROVEMENTS: - Clean URL namespacing (agent_base:marketplace, wallet:wallet) - Template organization by app responsibility - Removed deprecated CSS files (header.css) - Added utility classes (.hidden) - Updated all template references to new URL structure - Comprehensive CLAUDE.md documentation updates TECHNICAL CHANGES: - Templates moved: marketplace.html, agent_detail.html → agent_base/ - Templates moved: wallet*.html → wallet/ - New files: agent_base/views.py, agent_base/urls.py, wallet/urls.py - Updated main urls.py routing configuration - Fixed Django system checks and namespace conflicts - Verified all functionality with test suite This reorganization follows Django best practices with single responsibility principle, making the codebase more maintainable and scalable. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
e2ad1f84e1
commit
bfdef5b658
225
CLAUDE.md
Normal file
225
CLAUDE.md
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
NetCop Hub is a Django-based AI agent marketplace platform where users can purchase and interact with specialized AI agents. The system supports both webhook-based and API-based agents with integrated payment processing via Stripe.
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
### Environment Setup
|
||||||
|
```bash
|
||||||
|
# Create and activate virtual environment
|
||||||
|
python -m venv venv
|
||||||
|
source venv/bin/activate # Linux/Mac
|
||||||
|
# or
|
||||||
|
venv\Scripts\activate # Windows
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Operations
|
||||||
|
```bash
|
||||||
|
# Check database configuration
|
||||||
|
python manage.py check_db
|
||||||
|
|
||||||
|
# Create and apply migrations
|
||||||
|
python manage.py makemigrations
|
||||||
|
python manage.py migrate
|
||||||
|
|
||||||
|
# Backup user data
|
||||||
|
python manage.py backup_users --action info
|
||||||
|
|
||||||
|
# Populate agent catalog
|
||||||
|
python manage.py populate_agents
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development Server
|
||||||
|
```bash
|
||||||
|
# Quick start (recommended - handles migrations and environment)
|
||||||
|
./run_dev.sh
|
||||||
|
|
||||||
|
# Manual start
|
||||||
|
python manage.py runserver
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
```bash
|
||||||
|
# Run specific agent tests
|
||||||
|
python tests/test_weather_agent.py
|
||||||
|
python tests/test_five_whys_webhook.py
|
||||||
|
|
||||||
|
# Test homepage functionality
|
||||||
|
python tests/test_homepage.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Management Commands
|
||||||
|
```bash
|
||||||
|
# Create new agent
|
||||||
|
python manage.py create_agent
|
||||||
|
|
||||||
|
# Create test user
|
||||||
|
python manage.py create_user
|
||||||
|
|
||||||
|
# Reset database (development only)
|
||||||
|
python manage.py reset_database
|
||||||
|
|
||||||
|
# Test webhook functionality
|
||||||
|
python manage.py test_webhook
|
||||||
|
|
||||||
|
# Cleanup uploaded files
|
||||||
|
python manage.py cleanup_uploads
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Agent System Architecture (`agent_base/`)
|
||||||
|
|
||||||
|
**Centralized Agent Management:**
|
||||||
|
- `agent_base/models.py` - `BaseAgent` model for marketplace catalog
|
||||||
|
- `agent_base/processors.py` - `BaseAgentProcessor` abstract class for agent interactions
|
||||||
|
- `agent_base/views.py` - Marketplace and agent discovery views
|
||||||
|
- `agent_base/urls.py` - Agent system URL routing
|
||||||
|
- `agent_base/generators/` - Template generation system for creating new agents
|
||||||
|
- `templates/agent_base/` - Marketplace and agent catalog templates
|
||||||
|
|
||||||
|
**Agent Types:**
|
||||||
|
1. **Webhook Agents** - Process requests via external webhook APIs (e.g., weather_reporter)
|
||||||
|
2. **API Agents** - Direct API integration for immediate responses
|
||||||
|
|
||||||
|
**Individual Agent Apps:**
|
||||||
|
Each agent is a separate Django app following this structure:
|
||||||
|
- `models.py` - Agent-specific request/response models
|
||||||
|
- `processor.py` - Inherits from `BaseAgentProcessor`, implements specific logic
|
||||||
|
- `views.py` - Agent detail page and request handling
|
||||||
|
- `templates/[agent_name]/detail.html` - Agent interface
|
||||||
|
- `urls.py` - Agent-specific URL routing
|
||||||
|
|
||||||
|
### Core System Architecture
|
||||||
|
|
||||||
|
**Authentication System (`authentication/`):**
|
||||||
|
- Custom User model with wallet integration
|
||||||
|
- Password reset functionality with email tokens
|
||||||
|
- Profile management
|
||||||
|
|
||||||
|
**Payment System (`wallet/`):**
|
||||||
|
- Stripe integration for payments
|
||||||
|
- User balance tracking
|
||||||
|
- Transaction history
|
||||||
|
|
||||||
|
**Core App (`core/`):**
|
||||||
|
- Homepage and platform overview
|
||||||
|
- Pricing page for non-authenticated users
|
||||||
|
- Platform-wide functionality only (no business logic)
|
||||||
|
|
||||||
|
**Agent Base App (`agent_base/`):**
|
||||||
|
- Agent marketplace and catalog views
|
||||||
|
- Agent discovery and filtering
|
||||||
|
- Cross-agent functionality and API endpoints
|
||||||
|
|
||||||
|
**Wallet App (`wallet/`):**
|
||||||
|
- Complete payment system with Stripe integration
|
||||||
|
- Wallet dashboard and transaction history
|
||||||
|
- Payment processing and webhook handling
|
||||||
|
|
||||||
|
### URL Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
/ # Homepage (core app)
|
||||||
|
/pricing/ # Pricing page (core app)
|
||||||
|
/marketplace/ # Agent marketplace (agent_base app)
|
||||||
|
/agents/<slug>/ # Agent detail redirect (agent_base app)
|
||||||
|
/auth/ # Authentication (login, register, profile)
|
||||||
|
/wallet/ # Wallet management and top-up (wallet app)
|
||||||
|
/wallet/stripe/ # Stripe webhooks and debug (wallet app)
|
||||||
|
/agents/[agent-slug]/ # Individual agent pages (individual apps)
|
||||||
|
/admin/ # Django admin
|
||||||
|
/api/agents/ # Agent API endpoint (agent_base app)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Template Architecture
|
||||||
|
|
||||||
|
**Template Hierarchy:**
|
||||||
|
- `templates/base.html` - Main layout with navigation and auth
|
||||||
|
- `templates/components/` - Reusable components (agent_header, wallet_card, etc.)
|
||||||
|
- `templates/core/` - Platform pages (homepage, pricing)
|
||||||
|
- `templates/agent_base/` - Agent marketplace and catalog
|
||||||
|
- `templates/wallet/` - Payment and wallet management
|
||||||
|
- `templates/authentication/` - User authentication pages
|
||||||
|
- Agent-specific templates in individual app directories
|
||||||
|
|
||||||
|
**CSS Architecture:**
|
||||||
|
- `base.css` - Global styles and CSS variables
|
||||||
|
- `agent-base.css` - Agent page styling
|
||||||
|
- `header-component.css` - Header styling (replaces deprecated header.css)
|
||||||
|
- Component-specific CSS files
|
||||||
|
|
||||||
|
### Database Design
|
||||||
|
|
||||||
|
**Key Models:**
|
||||||
|
- `BaseAgent` - Agent catalog and marketplace data
|
||||||
|
- `User` - Extended Django user with wallet functionality
|
||||||
|
- Agent-specific request models (e.g., `WeatherReportAgentRequest`)
|
||||||
|
|
||||||
|
### Environment Configuration
|
||||||
|
|
||||||
|
Required environment variables (see `.env.example`):
|
||||||
|
- `SECRET_KEY` - Django secret key
|
||||||
|
- `DEBUG` - Development mode flag
|
||||||
|
- Stripe keys for payment processing
|
||||||
|
- Email configuration for password reset
|
||||||
|
|
||||||
|
### Development Workflow
|
||||||
|
|
||||||
|
1. **Adding New Agent:**
|
||||||
|
- Use `python manage.py create_agent` command
|
||||||
|
- Follow existing agent patterns (inherit from `BaseAgentProcessor`)
|
||||||
|
- Add URL routing in main `urls.py`
|
||||||
|
- Agent will automatically appear in marketplace via `BaseAgent` model
|
||||||
|
|
||||||
|
2. **Modifying Templates:**
|
||||||
|
- Check existing components in `templates/components/`
|
||||||
|
- Follow CSS variable system defined in `base.css`
|
||||||
|
- Use `.hidden` utility class instead of inline `style="display:none"`
|
||||||
|
- Respect app-specific template organization (core, agent_base, wallet, etc.)
|
||||||
|
|
||||||
|
3. **Database Changes:**
|
||||||
|
- Always run migrations after model changes
|
||||||
|
- Use `check_db` command to verify configuration
|
||||||
|
- Test with `populate_agents` to ensure agent catalog works
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
|
||||||
|
- **Railway.app** integration via `railway.json`
|
||||||
|
- Production settings in `netcop_hub/production_settings.py`
|
||||||
|
- Static files served via WhiteNoise
|
||||||
|
- Database migrations run automatically on deployment
|
||||||
|
|
||||||
|
### File Upload Handling
|
||||||
|
|
||||||
|
- `media/uploads/[agent_name]/` - User uploaded files
|
||||||
|
- Cleanup command available: `python manage.py cleanup_uploads`
|
||||||
|
- Files are processed by individual agent processors
|
||||||
|
|
||||||
|
### Architecture Principles
|
||||||
|
|
||||||
|
**Single Responsibility:**
|
||||||
|
- `core` - Platform presentation and static pages only
|
||||||
|
- `agent_base` - Agent marketplace, catalog, and cross-agent functionality
|
||||||
|
- `wallet` - Complete payment system with Stripe integration
|
||||||
|
- Individual agent apps - Specific agent logic and interfaces
|
||||||
|
|
||||||
|
**URL Namespacing:**
|
||||||
|
- Use `agent_base:marketplace` for marketplace links
|
||||||
|
- Use `wallet:wallet` for wallet-related links
|
||||||
|
- Use `core:homepage` for platform homepage
|
||||||
|
- Individual agents have their own URL namespaces
|
||||||
|
|
||||||
|
**Template Organization:**
|
||||||
|
- Templates are organized by app responsibility
|
||||||
|
- Use proper URL namespacing in templates
|
||||||
|
- Marketplace functionality is in `agent_base` app, not `core`
|
||||||
|
|
||||||
|
Always run `python manage.py check_db` before making database-related changes to ensure proper configuration.
|
||||||
@ -1,220 +0,0 @@
|
|||||||
# Header Optimization & CSS Architecture Redesign
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This document outlines the comprehensive header optimization and CSS architecture redesign implemented to resolve font inconsistencies and improve maintainability across the NetCop Hub platform.
|
|
||||||
|
|
||||||
## Problem Statement
|
|
||||||
|
|
||||||
### Initial Issues
|
|
||||||
1. **Font Weight Inconsistency**: Navigation text appeared bold on pricing page but normal on marketplace page
|
|
||||||
2. **CSS Architecture Fragmentation**: Multiple conflicting CSS files with different font stacks
|
|
||||||
3. **Template Bloat**: 476 lines of inline CSS in pricing.html template
|
|
||||||
4. **Font Inheritance Conflicts**: Different font fallbacks causing rendering differences
|
|
||||||
5. **No Active State Indicator**: No visual indication of current page in navigation
|
|
||||||
|
|
||||||
### Root Cause Analysis
|
|
||||||
- **Marketplace Page**: Used `'Inter', Arial, sans-serif` font stack
|
|
||||||
- **Pricing Page**: Used `'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif` font stack
|
|
||||||
- Different fallback fonts (`Arial` vs system fonts) caused Inter to render with different weights
|
|
||||||
- Global CSS selectors in `agent-base.css` were overriding header component styles
|
|
||||||
|
|
||||||
## Solution Architecture
|
|
||||||
|
|
||||||
### 1. Unified Font System
|
|
||||||
**Before:**
|
|
||||||
```css
|
|
||||||
/* base.css */
|
|
||||||
body { font-family: 'Inter', Arial, sans-serif; }
|
|
||||||
|
|
||||||
/* agent-base.css */
|
|
||||||
--font-primary: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
||||||
```
|
|
||||||
|
|
||||||
**After:**
|
|
||||||
```css
|
|
||||||
/* All CSS files now use consistent font stack */
|
|
||||||
font-family: 'Inter', Arial, sans-serif;
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. CSS Loading Order Optimization
|
|
||||||
**Loading Sequence:**
|
|
||||||
1. `base.css` (global styles)
|
|
||||||
2. Page-specific CSS via `{% block extra_css %}`
|
|
||||||
3. `header-component.css` (always loads last)
|
|
||||||
|
|
||||||
### 3. Component-Based CSS Architecture
|
|
||||||
**Structure:**
|
|
||||||
```
|
|
||||||
static/css/
|
|
||||||
├── base.css # Global variables and base styles
|
|
||||||
├── header-component.css # Header-specific styles (loads last)
|
|
||||||
├── agent-base.css # Agent page base styles
|
|
||||||
├── pricing.css # Pricing page styles (extracted from inline)
|
|
||||||
├── marketplace.css # Marketplace page styles
|
|
||||||
└── ...
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Font Loading Standardization
|
|
||||||
**Implementation in base.html:**
|
|
||||||
```html
|
|
||||||
<!-- Unified Font Loading - Single Source of Truth -->
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link rel="preload" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" as="style">
|
|
||||||
```
|
|
||||||
|
|
||||||
## Technical Implementation
|
|
||||||
|
|
||||||
### Header Component CSS Structure
|
|
||||||
```css
|
|
||||||
/* Clean browser reset */
|
|
||||||
.header-component * {
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
-webkit-touch-callout: none;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Navigation links with consistent font */
|
|
||||||
.header-component .nav-link {
|
|
||||||
color: var(--nav-text) !important;
|
|
||||||
font-weight: 400 !important;
|
|
||||||
font-family: 'Inter', Arial, sans-serif !important;
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Active page indicator */
|
|
||||||
.header-component .nav-link.active::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
bottom: -2px;
|
|
||||||
height: 2px;
|
|
||||||
background: var(--nav-text-hover);
|
|
||||||
border-radius: 1px;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Template Integration
|
|
||||||
```html
|
|
||||||
<!-- base.html navigation with active states -->
|
|
||||||
<nav class="header-nav">
|
|
||||||
<a href="{% url 'core:homepage' %}"
|
|
||||||
class="nav-link {% if request.resolver_match.url_name == 'homepage' %}active{% endif %}">
|
|
||||||
Home
|
|
||||||
</a>
|
|
||||||
<!-- ... -->
|
|
||||||
</nav>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Improvements
|
|
||||||
|
|
||||||
### Before Optimization
|
|
||||||
- ❌ 476 lines of inline CSS in pricing.html
|
|
||||||
- ❌ Duplicate font imports across templates
|
|
||||||
- ❌ CSS conflicts requiring `!important` hacks
|
|
||||||
- ❌ Inconsistent font rendering across pages
|
|
||||||
|
|
||||||
### After Optimization
|
|
||||||
- ✅ External CSS files with browser caching
|
|
||||||
- ✅ Single font loading source in base.html
|
|
||||||
- ✅ Clean CSS architecture with proper specificity
|
|
||||||
- ✅ Consistent font rendering across all pages
|
|
||||||
- ✅ Subtle active page indicators
|
|
||||||
|
|
||||||
## Files Modified
|
|
||||||
|
|
||||||
### Templates
|
|
||||||
- `templates/base.html`: Added unified font loading, restored active class logic
|
|
||||||
- `templates/core/pricing.html`: Removed inline CSS, added external CSS reference
|
|
||||||
|
|
||||||
### CSS Files
|
|
||||||
- `static/css/header-component.css`: Added active state indicators, font consistency
|
|
||||||
- `static/css/agent-base.css`: Unified font stack, improved scoping
|
|
||||||
- `static/css/pricing.css`: **NEW FILE** - Extracted from inline styles
|
|
||||||
|
|
||||||
### Key Changes Summary
|
|
||||||
1. **Font Unification**: All pages now use `'Inter', Arial, sans-serif`
|
|
||||||
2. **CSS Extraction**: 476 lines moved from inline to external file
|
|
||||||
3. **Active States**: Added thin line indicators for current page
|
|
||||||
4. **Browser Reset**: Improved cross-browser consistency
|
|
||||||
5. **Loading Order**: Optimized CSS cascade for reliability
|
|
||||||
|
|
||||||
## Visual Design
|
|
||||||
|
|
||||||
### Active Page Indicator
|
|
||||||
- **Style**: 2px thin line under navigation text
|
|
||||||
- **Color**: Blue (`var(--nav-text-hover)`)
|
|
||||||
- **Position**: 2px below text with rounded corners
|
|
||||||
- **Behavior**: Only appears on current page, no layout shift
|
|
||||||
|
|
||||||
### Navigation States
|
|
||||||
- **Normal**: Gray text (`#6b7280`), no background
|
|
||||||
- **Hover**: Blue text on hover (temporary)
|
|
||||||
- **Active**: Gray text with blue underline
|
|
||||||
- **Focus**: Clean outline for accessibility
|
|
||||||
|
|
||||||
## Browser Compatibility
|
|
||||||
|
|
||||||
### Font Rendering
|
|
||||||
- **Primary**: Inter font (loaded from Google Fonts)
|
|
||||||
- **Fallback**: Arial (consistent across all browsers)
|
|
||||||
- **Smoothing**: Optimized for all webkit and moz browsers
|
|
||||||
|
|
||||||
### CSS Features Used
|
|
||||||
- CSS Custom Properties (supported in all modern browsers)
|
|
||||||
- Flexbox and CSS Grid (well-supported)
|
|
||||||
- `::after` pseudo-elements (universal support)
|
|
||||||
|
|
||||||
## Maintenance Guidelines
|
|
||||||
|
|
||||||
### Adding New Pages
|
|
||||||
1. Create page-specific CSS file in `static/css/`
|
|
||||||
2. Include in template's `{% block extra_css %}`
|
|
||||||
3. Use consistent font stack: `'Inter', Arial, sans-serif`
|
|
||||||
4. Avoid global selectors that might affect header
|
|
||||||
|
|
||||||
### CSS Best Practices
|
|
||||||
1. **Loading Order**: Page CSS first, header CSS last
|
|
||||||
2. **Font Consistency**: Always use unified font stack
|
|
||||||
3. **Specificity**: Use component-based selectors
|
|
||||||
4. **Variables**: Leverage CSS custom properties
|
|
||||||
|
|
||||||
### Testing Checklist
|
|
||||||
- [ ] Navigation font appears identical across all pages
|
|
||||||
- [ ] Active page shows thin blue underline
|
|
||||||
- [ ] No layout shifts when clicking navigation
|
|
||||||
- [ ] Hover states work correctly
|
|
||||||
- [ ] Mobile navigation functions properly
|
|
||||||
|
|
||||||
## Performance Metrics
|
|
||||||
|
|
||||||
### Improvements Achieved
|
|
||||||
- **CSS Size Reduction**: 476 lines removed from HTML
|
|
||||||
- **Caching**: External CSS files now cacheable
|
|
||||||
- **Loading**: Single font source eliminates duplicate requests
|
|
||||||
- **Rendering**: Consistent font rendering eliminates reflows
|
|
||||||
|
|
||||||
### Load Time Impact
|
|
||||||
- **Before**: Inline CSS parsed on every page load
|
|
||||||
- **After**: External CSS cached after first load
|
|
||||||
- **Font Loading**: Preload optimization for faster rendering
|
|
||||||
|
|
||||||
## Future Enhancements
|
|
||||||
|
|
||||||
### Potential Improvements
|
|
||||||
1. **CSS Modules**: Consider CSS-in-JS for component isolation
|
|
||||||
2. **Theme System**: Expand CSS custom properties for dark/light themes
|
|
||||||
3. **Animation**: Add subtle transitions for active state changes
|
|
||||||
4. **A11y**: Enhanced focus management and screen reader support
|
|
||||||
|
|
||||||
## Conclusion
|
|
||||||
|
|
||||||
The header optimization successfully resolved font inconsistencies while establishing a robust, maintainable CSS architecture. The solution provides:
|
|
||||||
|
|
||||||
- ✅ **Consistent Visual Experience**: Identical navigation across all pages
|
|
||||||
- ✅ **Better Performance**: Optimized loading and caching
|
|
||||||
- ✅ **Improved Maintainability**: Clean, organized CSS structure
|
|
||||||
- ✅ **Enhanced UX**: Clear active page indicators
|
|
||||||
- ✅ **Future-Proof Architecture**: Scalable design system
|
|
||||||
|
|
||||||
This foundation ensures reliable header behavior and provides a solid base for future UI development.
|
|
||||||
10
agent_base/urls.py
Normal file
10
agent_base/urls.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = 'agent_base'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('marketplace/', views.marketplace_view, name='marketplace'),
|
||||||
|
path('agents/<slug:agent_slug>/', views.agent_detail_view, name='agent_detail'),
|
||||||
|
path('api/agents/', views.agents_api_view, name='agents_api'),
|
||||||
|
]
|
||||||
75
agent_base/views.py
Normal file
75
agent_base/views.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
from django.shortcuts import render, redirect, get_object_or_404
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.db.models import Q
|
||||||
|
from .models import BaseAgent
|
||||||
|
|
||||||
|
|
||||||
|
def marketplace_view(request):
|
||||||
|
"""Professional marketplace view with agent system"""
|
||||||
|
# Get all agents for marketplace with optimized query
|
||||||
|
agents_queryset = BaseAgent.objects.filter(is_active=True).select_related().order_by('category', 'name')
|
||||||
|
|
||||||
|
# Filter by category if specified
|
||||||
|
category = request.GET.get('category')
|
||||||
|
if category:
|
||||||
|
agents_queryset = agents_queryset.filter(category=category)
|
||||||
|
|
||||||
|
# Get agents and categories in single query
|
||||||
|
agents = list(agents_queryset)
|
||||||
|
categories = BaseAgent.objects.filter(is_active=True).values_list('category', 'category').distinct()
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'user_balance': request.user.wallet_balance if request.user.is_authenticated else 0,
|
||||||
|
'agents': agents,
|
||||||
|
'categories': categories,
|
||||||
|
'selected_category': category,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, 'agent_base/marketplace.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
def agent_detail_view(request, agent_slug):
|
||||||
|
"""Agent detail view - redirect to specific agent app"""
|
||||||
|
try:
|
||||||
|
agent = BaseAgent.objects.get(slug=agent_slug, is_active=True)
|
||||||
|
# Redirect to the specific agent app URL
|
||||||
|
if agent_slug == 'weather-reporter':
|
||||||
|
return redirect('/agents/weather-reporter/')
|
||||||
|
else:
|
||||||
|
# For other agents, redirect to marketplace for now
|
||||||
|
messages.info(request, f'Agent "{agent.name}" page not yet available.')
|
||||||
|
return redirect('agent_base:marketplace')
|
||||||
|
except BaseAgent.DoesNotExist:
|
||||||
|
messages.error(request, 'Agent not found')
|
||||||
|
return redirect('agent_base:marketplace')
|
||||||
|
|
||||||
|
|
||||||
|
def agents_api_view(request):
|
||||||
|
"""API endpoint for agents list"""
|
||||||
|
agents = BaseAgent.objects.filter(is_active=True)
|
||||||
|
|
||||||
|
# Filter by category if specified
|
||||||
|
category = request.GET.get('category')
|
||||||
|
if category:
|
||||||
|
agents = agents.filter(category=category)
|
||||||
|
|
||||||
|
agents_data = []
|
||||||
|
for agent in agents:
|
||||||
|
agents_data.append({
|
||||||
|
'id': str(agent.id),
|
||||||
|
'name': agent.name,
|
||||||
|
'slug': agent.slug,
|
||||||
|
'description': agent.description,
|
||||||
|
'category': agent.category,
|
||||||
|
'price': float(agent.price),
|
||||||
|
'icon': agent.icon,
|
||||||
|
'rating': float(agent.rating),
|
||||||
|
'review_count': agent.review_count,
|
||||||
|
'agent_type': agent.agent_type,
|
||||||
|
})
|
||||||
|
|
||||||
|
return JsonResponse({
|
||||||
|
'agents': agents_data,
|
||||||
|
'total_count': len(agents_data),
|
||||||
|
})
|
||||||
@ -5,14 +5,5 @@ app_name = 'core'
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', views.homepage_view, name='homepage'),
|
path('', views.homepage_view, name='homepage'),
|
||||||
path('marketplace/', views.marketplace_view, name='marketplace'),
|
|
||||||
path('pricing/', views.pricing_view, name='pricing'),
|
path('pricing/', views.pricing_view, name='pricing'),
|
||||||
path('agents/<slug:agent_slug>/', views.agent_detail_view, name='agent_detail'),
|
|
||||||
path('wallet/', views.wallet_view, name='wallet'),
|
|
||||||
path('wallet/topup/', views.wallet_topup_view, name='wallet_topup'),
|
|
||||||
path('wallet/top-up/success/', views.wallet_topup_success_view, name='wallet_topup_success'),
|
|
||||||
path('wallet/top-up/cancel/', views.wallet_topup_cancel_view, name='wallet_topup_cancel'),
|
|
||||||
path('stripe/debug/', views.stripe_debug_view, name='stripe_debug'),
|
|
||||||
path('stripe/webhook/', views.stripe_webhook_view, name='stripe_webhook'),
|
|
||||||
path('api/agents/', views.agents_api_view, name='agents_api'),
|
|
||||||
]
|
]
|
||||||
333
core/views.py
333
core/views.py
@ -1,21 +1,6 @@
|
|||||||
from django.shortcuts import render, redirect, get_object_or_404
|
from django.shortcuts import render, redirect
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib import messages
|
|
||||||
from django.http import JsonResponse, HttpResponse
|
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
from django.views.decorators.http import require_http_methods
|
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
from django.views import View
|
|
||||||
from django.db.models import Q
|
|
||||||
from django.template.loader import get_template
|
|
||||||
from django.template import TemplateDoesNotExist
|
|
||||||
from agent_base.models import BaseAgent
|
from agent_base.models import BaseAgent
|
||||||
from wallet.stripe_handler import StripePaymentHandler
|
|
||||||
from wallet.models import WalletTransaction
|
|
||||||
import json
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
|
|
||||||
def homepage_view(request):
|
def homepage_view(request):
|
||||||
"""Homepage view with agent system"""
|
"""Homepage view with agent system"""
|
||||||
# Get featured agents for homepage
|
# Get featured agents for homepage
|
||||||
@ -27,37 +12,11 @@ def homepage_view(request):
|
|||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'core/homepage.html', context)
|
return render(request, 'core/homepage.html', context)
|
||||||
|
|
||||||
|
|
||||||
def marketplace_view(request):
|
|
||||||
"""Professional marketplace view with agent system"""
|
|
||||||
# Get all agents for marketplace with optimized query
|
|
||||||
agents_queryset = BaseAgent.objects.filter(is_active=True).select_related().order_by('category', 'name')
|
|
||||||
|
|
||||||
# Filter by category if specified
|
|
||||||
category = request.GET.get('category')
|
|
||||||
if category:
|
|
||||||
agents_queryset = agents_queryset.filter(category=category)
|
|
||||||
|
|
||||||
# Get agents and categories in single query
|
|
||||||
agents = list(agents_queryset)
|
|
||||||
categories = BaseAgent.objects.filter(is_active=True).values_list('category', 'category').distinct()
|
|
||||||
|
|
||||||
context = {
|
|
||||||
'user_balance': request.user.wallet_balance if request.user.is_authenticated else 0,
|
|
||||||
'agents': agents,
|
|
||||||
'categories': categories,
|
|
||||||
'selected_category': category,
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(request, 'core/marketplace.html', context)
|
|
||||||
|
|
||||||
|
|
||||||
def pricing_view(request):
|
def pricing_view(request):
|
||||||
"""Pricing page for non-logged-in users"""
|
"""Pricing page for non-logged-in users"""
|
||||||
# If user is already logged in, redirect to wallet top-up
|
# If user is already logged in, redirect to wallet top-up
|
||||||
if request.user.is_authenticated:
|
if request.user.is_authenticated:
|
||||||
return redirect('core:wallet_topup')
|
return redirect('wallet:wallet_topup')
|
||||||
|
|
||||||
# Get sample agents to show pricing context
|
# Get sample agents to show pricing context
|
||||||
sample_agents = BaseAgent.objects.filter(is_active=True).order_by('name')[:4]
|
sample_agents = BaseAgent.objects.filter(is_active=True).order_by('name')[:4]
|
||||||
@ -67,291 +26,3 @@ def pricing_view(request):
|
|||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'core/pricing.html', context)
|
return render(request, 'core/pricing.html', context)
|
||||||
|
|
||||||
|
|
||||||
def agent_detail_view(request, agent_slug):
|
|
||||||
"""Agent detail view - redirect to specific agent app"""
|
|
||||||
try:
|
|
||||||
agent = BaseAgent.objects.get(slug=agent_slug, is_active=True)
|
|
||||||
# Redirect to the specific agent app URL
|
|
||||||
if agent_slug == 'weather-reporter':
|
|
||||||
return redirect('/agents/weather-reporter/')
|
|
||||||
else:
|
|
||||||
# For other agents, redirect to marketplace for now
|
|
||||||
messages.info(request, f'Agent "{agent.name}" page not yet available.')
|
|
||||||
return redirect('core:marketplace')
|
|
||||||
except BaseAgent.DoesNotExist:
|
|
||||||
messages.error(request, 'Agent not found')
|
|
||||||
return redirect('core:marketplace')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def wallet_view(request):
|
|
||||||
"""Wallet management page"""
|
|
||||||
transactions = request.user.wallet_transactions.all()[:50]
|
|
||||||
|
|
||||||
# Calculate statistics
|
|
||||||
total_spent = sum(abs(t.amount) for t in transactions if t.type == 'agent_usage')
|
|
||||||
total_topped_up = sum(t.amount for t in transactions if t.type == 'top_up')
|
|
||||||
|
|
||||||
context = {
|
|
||||||
'transactions': transactions,
|
|
||||||
'total_spent': total_spent,
|
|
||||||
'total_topped_up': total_topped_up,
|
|
||||||
'current_balance': request.user.wallet_balance,
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(request, 'core/wallet.html', context)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def wallet_topup_view(request):
|
|
||||||
"""Wallet top-up page"""
|
|
||||||
if request.method == 'POST':
|
|
||||||
amount = request.POST.get('amount')
|
|
||||||
|
|
||||||
try:
|
|
||||||
amount = float(amount)
|
|
||||||
if amount not in [10, 50, 100, 500]:
|
|
||||||
messages.error(request, 'Invalid amount selected')
|
|
||||||
return redirect('core:wallet_topup')
|
|
||||||
|
|
||||||
# Create Stripe checkout session
|
|
||||||
stripe_handler = StripePaymentHandler()
|
|
||||||
session_data = stripe_handler.create_checkout_session(request.user, amount, request)
|
|
||||||
|
|
||||||
return redirect(session_data['payment_url'])
|
|
||||||
|
|
||||||
except (ValueError, TypeError):
|
|
||||||
messages.error(request, 'Invalid amount')
|
|
||||||
return redirect('core:wallet_topup')
|
|
||||||
|
|
||||||
return render(request, 'core/wallet_topup.html')
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def wallet_topup_success_view(request):
|
|
||||||
"""Payment success page with automatic payment verification (NO WEBHOOKS NEEDED)"""
|
|
||||||
session_id = request.GET.get('session_id')
|
|
||||||
|
|
||||||
if not session_id:
|
|
||||||
messages.error(request, 'No payment session found. Please contact support if you completed a payment.')
|
|
||||||
return redirect('core:wallet')
|
|
||||||
|
|
||||||
# Verify payment directly with Stripe API (bypasses webhook issues)
|
|
||||||
try:
|
|
||||||
from wallet.stripe_handler import StripePaymentHandler
|
|
||||||
stripe_handler = StripePaymentHandler()
|
|
||||||
|
|
||||||
print(f"💳 [SUCCESS PAGE] Verifying payment for session: {session_id}")
|
|
||||||
result = stripe_handler.verify_payment(session_id)
|
|
||||||
|
|
||||||
if result['success']:
|
|
||||||
if result['processed']:
|
|
||||||
messages.success(request, f'Payment successful! {result["amount"]} AED has been added to your wallet.')
|
|
||||||
print(f"✅ [SUCCESS PAGE] Payment verified and wallet updated for user {request.user.id}")
|
|
||||||
else:
|
|
||||||
messages.info(request, 'Payment already processed. Your wallet balance is up to date.')
|
|
||||||
print(f"ℹ️ [SUCCESS PAGE] Payment already processed for session {session_id}")
|
|
||||||
else:
|
|
||||||
messages.warning(request, f'Payment verification failed: {result.get("error", "Unknown error")}. Please contact support.')
|
|
||||||
print(f"❌ [SUCCESS PAGE] Payment verification failed: {result}")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"❌ [SUCCESS PAGE] Error verifying payment: {e}")
|
|
||||||
messages.error(request, 'Unable to verify payment. Please contact support if you completed a payment.')
|
|
||||||
|
|
||||||
return redirect('core:wallet')
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def wallet_topup_cancel_view(request):
|
|
||||||
"""Payment cancel page"""
|
|
||||||
messages.info(request, 'Payment was cancelled. No charges were made.')
|
|
||||||
return redirect('core:wallet_topup')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def stripe_debug_view(request):
|
|
||||||
"""Debug endpoint to show Stripe API configuration and test connectivity"""
|
|
||||||
import stripe
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
debug_info = {
|
|
||||||
'timestamp': datetime.datetime.now().isoformat(),
|
|
||||||
'user_id': request.user.id,
|
|
||||||
'user_email': request.user.email,
|
|
||||||
}
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Test Stripe API connectivity
|
|
||||||
print(f"🔍 [STRIPE DEBUG] Testing Stripe API connectivity...")
|
|
||||||
|
|
||||||
# Get API key info (masked)
|
|
||||||
api_key = settings.STRIPE_SECRET_KEY
|
|
||||||
debug_info['stripe_api_key_last4'] = api_key[-4:] if api_key else 'Not set'
|
|
||||||
debug_info['stripe_api_key_prefix'] = api_key[:7] if api_key else 'Not set'
|
|
||||||
debug_info['stripe_api_version'] = stripe.api_version
|
|
||||||
|
|
||||||
# Test account connectivity
|
|
||||||
try:
|
|
||||||
account = stripe.Account.retrieve()
|
|
||||||
debug_info['stripe_account'] = {
|
|
||||||
'id': account.id,
|
|
||||||
'email': account.email,
|
|
||||||
'display_name': account.display_name,
|
|
||||||
'country': account.country,
|
|
||||||
'default_currency': account.default_currency,
|
|
||||||
'business_profile': account.business_profile,
|
|
||||||
'charges_enabled': account.charges_enabled,
|
|
||||||
'payouts_enabled': account.payouts_enabled,
|
|
||||||
}
|
|
||||||
print(f"✅ [STRIPE DEBUG] Account connected: {account.id}")
|
|
||||||
except Exception as account_error:
|
|
||||||
debug_info['stripe_account_error'] = str(account_error)
|
|
||||||
print(f"❌ [STRIPE DEBUG] Account error: {account_error}")
|
|
||||||
|
|
||||||
# Test recent checkout sessions
|
|
||||||
try:
|
|
||||||
sessions = stripe.checkout.Session.list(limit=5)
|
|
||||||
debug_info['recent_sessions'] = []
|
|
||||||
for session in sessions.data:
|
|
||||||
debug_info['recent_sessions'].append({
|
|
||||||
'id': session.id,
|
|
||||||
'status': session.status,
|
|
||||||
'payment_status': session.payment_status,
|
|
||||||
'amount_total': session.amount_total,
|
|
||||||
'currency': session.currency,
|
|
||||||
'customer_email': session.customer_email,
|
|
||||||
'client_reference_id': session.client_reference_id,
|
|
||||||
'created': session.created,
|
|
||||||
'metadata': session.metadata,
|
|
||||||
})
|
|
||||||
print(f"✅ [STRIPE DEBUG] Retrieved {len(sessions.data)} recent sessions")
|
|
||||||
except Exception as sessions_error:
|
|
||||||
debug_info['sessions_error'] = str(sessions_error)
|
|
||||||
print(f"❌ [STRIPE DEBUG] Sessions error: {sessions_error}")
|
|
||||||
|
|
||||||
# Test recent payments
|
|
||||||
try:
|
|
||||||
charges = stripe.Charge.list(limit=5)
|
|
||||||
debug_info['recent_charges'] = []
|
|
||||||
for charge in charges.data:
|
|
||||||
debug_info['recent_charges'].append({
|
|
||||||
'id': charge.id,
|
|
||||||
'amount': charge.amount,
|
|
||||||
'currency': charge.currency,
|
|
||||||
'status': charge.status,
|
|
||||||
'paid': charge.paid,
|
|
||||||
'customer': charge.customer,
|
|
||||||
'description': charge.description,
|
|
||||||
'created': charge.created,
|
|
||||||
'metadata': charge.metadata,
|
|
||||||
})
|
|
||||||
print(f"✅ [STRIPE DEBUG] Retrieved {len(charges.data)} recent charges")
|
|
||||||
except Exception as charges_error:
|
|
||||||
debug_info['charges_error'] = str(charges_error)
|
|
||||||
print(f"❌ [STRIPE DEBUG] Charges error: {charges_error}")
|
|
||||||
|
|
||||||
debug_info['status'] = 'success'
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
debug_info['error'] = str(e)
|
|
||||||
debug_info['status'] = 'error'
|
|
||||||
print(f"❌ [STRIPE DEBUG] General error: {e}")
|
|
||||||
|
|
||||||
return JsonResponse(debug_info, indent=2)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
|
||||||
def stripe_webhook_view(request):
|
|
||||||
"""Handle Stripe webhook events with comprehensive logging"""
|
|
||||||
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
|
|
||||||
|
|
||||||
# Log everything for debugging
|
|
||||||
print(f"🎯 [{timestamp}] Stripe webhook received!")
|
|
||||||
print(f"🎯 Method: {request.method}")
|
|
||||||
print(f"🎯 Content-Type: {request.content_type}")
|
|
||||||
print(f"🎯 Remote IP: {request.META.get('REMOTE_ADDR', 'unknown')}")
|
|
||||||
print(f"🎯 User Agent: {request.META.get('HTTP_USER_AGENT', 'unknown')}")
|
|
||||||
print(f"🎯 Full headers: {dict(request.META)}")
|
|
||||||
|
|
||||||
# Store in webhook logs for the test page
|
|
||||||
webhook_log_entry = {
|
|
||||||
'timestamp': timestamp,
|
|
||||||
'method': request.method,
|
|
||||||
'headers': dict(request.META),
|
|
||||||
'body': request.body.decode('utf-8') if request.body else '',
|
|
||||||
'content_type': request.content_type,
|
|
||||||
'source': 'stripe_webhook',
|
|
||||||
'ip_address': request.META.get('REMOTE_ADDR', 'unknown'),
|
|
||||||
'user_agent': request.META.get('HTTP_USER_AGENT', 'unknown')
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add to webhook logs
|
|
||||||
webhook_logs.append(webhook_log_entry)
|
|
||||||
if len(webhook_logs) > 50:
|
|
||||||
webhook_logs.pop(0)
|
|
||||||
|
|
||||||
if request.method != 'POST':
|
|
||||||
print(f"❌ Invalid method: {request.method}")
|
|
||||||
return JsonResponse({'status': 'error', 'message': f'Method {request.method} not allowed'}, status=405)
|
|
||||||
|
|
||||||
payload = request.body
|
|
||||||
sig_header = request.META.get('HTTP_STRIPE_SIGNATURE')
|
|
||||||
|
|
||||||
print(f"📦 Payload length: {len(payload)} bytes")
|
|
||||||
print(f"📦 Payload preview: {payload[:200]}...")
|
|
||||||
print(f"🔐 Signature header: {sig_header is not None}")
|
|
||||||
print(f"🔐 Full signature header: {sig_header}")
|
|
||||||
|
|
||||||
# Always return success first to see if Stripe is reaching us
|
|
||||||
if not sig_header:
|
|
||||||
print(f"⚠️ No Stripe signature - might be a test request")
|
|
||||||
return JsonResponse({'status': 'received', 'message': 'No signature verification'})
|
|
||||||
|
|
||||||
stripe_handler = StripePaymentHandler()
|
|
||||||
result = stripe_handler.handle_webhook(payload, sig_header)
|
|
||||||
|
|
||||||
print(f"✅ Webhook result: {result}")
|
|
||||||
|
|
||||||
if result['success']:
|
|
||||||
return JsonResponse({'status': 'success'})
|
|
||||||
else:
|
|
||||||
return JsonResponse({'status': 'error', 'message': result['error']}, status=400)
|
|
||||||
|
|
||||||
|
|
||||||
def agents_api_view(request):
|
|
||||||
"""API endpoint for agents list"""
|
|
||||||
agents = BaseAgent.objects.filter(is_active=True)
|
|
||||||
|
|
||||||
# Filter by category if specified
|
|
||||||
category = request.GET.get('category')
|
|
||||||
if category:
|
|
||||||
agents = agents.filter(category=category)
|
|
||||||
|
|
||||||
agents_data = []
|
|
||||||
for agent in agents:
|
|
||||||
agents_data.append({
|
|
||||||
'id': str(agent.id),
|
|
||||||
'name': agent.name,
|
|
||||||
'slug': agent.slug,
|
|
||||||
'description': agent.description,
|
|
||||||
'category': agent.category,
|
|
||||||
'price': float(agent.price),
|
|
||||||
'icon': agent.icon,
|
|
||||||
'rating': float(agent.rating),
|
|
||||||
'review_count': agent.review_count,
|
|
||||||
'agent_type': agent.agent_type,
|
|
||||||
})
|
|
||||||
|
|
||||||
return JsonResponse({
|
|
||||||
'agents': agents_data,
|
|
||||||
'total_count': len(agents_data),
|
|
||||||
})
|
|
||||||
0
data_analyzer/management/__init__.py
Normal file
0
data_analyzer/management/__init__.py
Normal file
0
data_analyzer/management/commands/__init__.py
Normal file
0
data_analyzer/management/commands/__init__.py
Normal file
125
data_analyzer/management/commands/cleanup_uploads.py
Normal file
125
data_analyzer/management/commands/cleanup_uploads.py
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.utils import timezone
|
||||||
|
from datetime import timedelta
|
||||||
|
from data_analyzer.models import DataAnalysisAgentRequest
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Clean up old uploaded files from data analyzer'
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
'--age-hours',
|
||||||
|
type=int,
|
||||||
|
default=24,
|
||||||
|
help='Delete files older than this many hours (default: 24)'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--dry-run',
|
||||||
|
action='store_true',
|
||||||
|
help='Show what would be deleted without actually deleting'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--force-orphaned',
|
||||||
|
action='store_true',
|
||||||
|
help='Also delete orphaned files not associated with database records'
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
age_hours = options['age_hours']
|
||||||
|
dry_run = options['dry_run']
|
||||||
|
force_orphaned = options['force_orphaned']
|
||||||
|
|
||||||
|
cutoff_time = timezone.now() - timedelta(hours=age_hours)
|
||||||
|
|
||||||
|
self.stdout.write(f"Looking for files older than {age_hours} hours ({cutoff_time})")
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
self.stdout.write(self.style.WARNING("DRY RUN MODE - No files will be deleted"))
|
||||||
|
|
||||||
|
# Clean up files associated with old database records
|
||||||
|
old_requests = DataAnalysisAgentRequest.objects.filter(
|
||||||
|
created_at__lt=cutoff_time
|
||||||
|
)
|
||||||
|
|
||||||
|
deleted_count = 0
|
||||||
|
error_count = 0
|
||||||
|
|
||||||
|
for request in old_requests:
|
||||||
|
if request.data_file:
|
||||||
|
try:
|
||||||
|
file_path = request.data_file.path
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
if not dry_run:
|
||||||
|
os.remove(file_path)
|
||||||
|
self.stdout.write(f"Deleted: {file_path}")
|
||||||
|
else:
|
||||||
|
self.stdout.write(f"Would delete: {file_path}")
|
||||||
|
deleted_count += 1
|
||||||
|
else:
|
||||||
|
self.stdout.write(f"File already gone: {file_path}")
|
||||||
|
except Exception as e:
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.ERROR(f"Error deleting {request.data_file.path}: {e}")
|
||||||
|
)
|
||||||
|
error_count += 1
|
||||||
|
|
||||||
|
# Clean up orphaned files if requested
|
||||||
|
if force_orphaned:
|
||||||
|
self.stdout.write("Checking for orphaned files...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
from django.conf import settings
|
||||||
|
upload_path = os.path.join(settings.MEDIA_ROOT, 'uploads/data_analyzer/')
|
||||||
|
|
||||||
|
if os.path.exists(upload_path):
|
||||||
|
# Get all files in upload directory
|
||||||
|
all_files = glob.glob(os.path.join(upload_path, '*'))
|
||||||
|
|
||||||
|
# Get all files currently referenced in database
|
||||||
|
db_files = set()
|
||||||
|
for request in DataAnalysisAgentRequest.objects.filter(data_file__isnull=False):
|
||||||
|
if request.data_file:
|
||||||
|
try:
|
||||||
|
db_files.add(request.data_file.path)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Find orphaned files
|
||||||
|
for file_path in all_files:
|
||||||
|
if os.path.isfile(file_path) and file_path not in db_files:
|
||||||
|
file_age = timezone.now() - timezone.datetime.fromtimestamp(
|
||||||
|
os.path.getctime(file_path),
|
||||||
|
tz=timezone.get_current_timezone()
|
||||||
|
)
|
||||||
|
|
||||||
|
if file_age > timedelta(hours=age_hours):
|
||||||
|
if not dry_run:
|
||||||
|
os.remove(file_path)
|
||||||
|
self.stdout.write(f"Deleted orphaned file: {file_path}")
|
||||||
|
else:
|
||||||
|
self.stdout.write(f"Would delete orphaned file: {file_path}")
|
||||||
|
deleted_count += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.ERROR(f"Error checking orphaned files: {e}")
|
||||||
|
)
|
||||||
|
error_count += 1
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
if dry_run:
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS(f"DRY RUN: Would delete {deleted_count} files")
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS(f"Successfully deleted {deleted_count} files")
|
||||||
|
)
|
||||||
|
|
||||||
|
if error_count > 0:
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.ERROR(f"Encountered {error_count} errors")
|
||||||
|
)
|
||||||
@ -1,6 +1,9 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from agent_base.models import BaseAgentRequest, BaseAgentResponse
|
from agent_base.models import BaseAgentRequest, BaseAgentResponse
|
||||||
|
from django.db.models.signals import post_delete
|
||||||
|
from django.dispatch import receiver
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
class DataAnalysisAgentRequest(BaseAgentRequest):
|
class DataAnalysisAgentRequest(BaseAgentRequest):
|
||||||
@ -21,6 +24,20 @@ class DataAnalysisAgentRequest(BaseAgentRequest):
|
|||||||
input_text = models.TextField(blank=True, null=True)
|
input_text = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
|
|
||||||
|
def delete(self, *args, **kwargs):
|
||||||
|
"""Custom delete method to clean up uploaded file"""
|
||||||
|
# Delete the file before deleting the database record
|
||||||
|
if self.data_file:
|
||||||
|
try:
|
||||||
|
if os.path.exists(self.data_file.path):
|
||||||
|
os.remove(self.data_file.path)
|
||||||
|
print(f"Deleted file during model deletion: {self.data_file.path}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Warning - Failed to delete file during model deletion: {e}")
|
||||||
|
|
||||||
|
# Call the parent delete method
|
||||||
|
super().delete(*args, **kwargs)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'data_analyzer_requests'
|
db_table = 'data_analyzer_requests'
|
||||||
verbose_name = 'Data Analysis Agent Request'
|
verbose_name = 'Data Analysis Agent Request'
|
||||||
@ -48,4 +65,16 @@ class DataAnalysisAgentResponse(BaseAgentResponse):
|
|||||||
class Meta:
|
class Meta:
|
||||||
db_table = 'data_analyzer_responses'
|
db_table = 'data_analyzer_responses'
|
||||||
verbose_name = 'Data Analysis Agent Response'
|
verbose_name = 'Data Analysis Agent Response'
|
||||||
verbose_name_plural = 'Data Analysis Agent Responses'
|
verbose_name_plural = 'Data Analysis Agent Responses'
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(post_delete, sender=DataAnalysisAgentRequest)
|
||||||
|
def cleanup_data_file(sender, instance, **kwargs):
|
||||||
|
"""Signal handler to ensure uploaded files are deleted when request is deleted"""
|
||||||
|
if instance.data_file:
|
||||||
|
try:
|
||||||
|
if os.path.exists(instance.data_file.path):
|
||||||
|
os.remove(instance.data_file.path)
|
||||||
|
print(f"Signal cleanup: Deleted file {instance.data_file.path}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Signal cleanup warning - Failed to delete file: {e}")
|
||||||
@ -5,6 +5,7 @@ from .models import DataAnalysisAgentRequest, DataAnalysisAgentResponse
|
|||||||
import json
|
import json
|
||||||
import requests
|
import requests
|
||||||
import time
|
import time
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
class DataAnalysisAgentProcessor(StandardWebhookProcessor):
|
class DataAnalysisAgentProcessor(StandardWebhookProcessor):
|
||||||
@ -29,6 +30,20 @@ class DataAnalysisAgentProcessor(StandardWebhookProcessor):
|
|||||||
|
|
||||||
return "\n".join(text_parts).strip()
|
return "\n".join(text_parts).strip()
|
||||||
|
|
||||||
|
def _cleanup_uploaded_file(self, request_obj):
|
||||||
|
"""Delete the uploaded file after processing to save storage and protect privacy"""
|
||||||
|
if request_obj and request_obj.data_file:
|
||||||
|
try:
|
||||||
|
file_path = request_obj.data_file.path
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
os.remove(file_path)
|
||||||
|
print(f"{self.agent_slug}: Successfully deleted uploaded file: {file_path}")
|
||||||
|
else:
|
||||||
|
print(f"{self.agent_slug}: File already deleted or doesn't exist: {file_path}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{self.agent_slug}: Warning - Failed to delete uploaded file: {e}")
|
||||||
|
# Don't raise exception as this is cleanup, not critical functionality
|
||||||
|
|
||||||
def make_request(self, data, timeout=60):
|
def make_request(self, data, timeout=60):
|
||||||
"""Override to send PDF file as binary data instead of JSON"""
|
"""Override to send PDF file as binary data instead of JSON"""
|
||||||
try:
|
try:
|
||||||
@ -169,6 +184,9 @@ class DataAnalysisAgentProcessor(StandardWebhookProcessor):
|
|||||||
request_obj.processed_at = timezone.now()
|
request_obj.processed_at = timezone.now()
|
||||||
request_obj.save()
|
request_obj.save()
|
||||||
|
|
||||||
|
# Cleanup uploaded file after successful processing
|
||||||
|
self._cleanup_uploaded_file(request_obj)
|
||||||
|
|
||||||
return response_obj
|
return response_obj
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -193,4 +211,7 @@ class DataAnalysisAgentProcessor(StandardWebhookProcessor):
|
|||||||
error_response.processing_time = response_data.get('processing_time', 0) if response_data else 0
|
error_response.processing_time = response_data.get('processing_time', 0) if response_data else 0
|
||||||
error_response.save()
|
error_response.save()
|
||||||
|
|
||||||
|
# Cleanup uploaded file even on error to prevent accumulation
|
||||||
|
self._cleanup_uploaded_file(request_obj)
|
||||||
|
|
||||||
raise Exception(f"Failed to process Data Analysis Agent response: {e}")
|
raise Exception(f"Failed to process Data Analysis Agent response: {e}")
|
||||||
@ -4,6 +4,7 @@
|
|||||||
{% block title %}Data Analyzer - NetCop AI Hub{% endblock %}
|
{% block title %}Data Analyzer - NetCop AI Hub{% endblock %}
|
||||||
|
|
||||||
{% block extra_css %}
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'css/agent-base.css' %}">
|
||||||
<style>
|
<style>
|
||||||
/* Data Analyzer Agent - Optimized Styles */
|
/* Data Analyzer Agent - Optimized Styles */
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||||
@ -1230,7 +1231,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
document.body.setAttribute('data-user-authenticated', '{{ user.is_authenticated|yesno:"true,false" }}');
|
document.body.setAttribute('data-user-authenticated', '{{ user.is_authenticated|yesno:"true,false" }}');
|
||||||
document.body.setAttribute('data-login-url', '{% url "authentication:login" %}');
|
document.body.setAttribute('data-login-url', '{% url "authentication:login" %}');
|
||||||
document.body.setAttribute('data-wallet-balance', '{{ user.wallet_balance|default:0 }}');
|
document.body.setAttribute('data-wallet-balance', '{{ user.wallet_balance|default:0 }}');
|
||||||
document.body.setAttribute('data-wallet-url', '{% url "core:wallet" %}');
|
document.body.setAttribute('data-wallet-url', '{% url "wallet:wallet" %}');
|
||||||
|
|
||||||
// Initialize radio selection
|
// Initialize radio selection
|
||||||
selectRadio('summary');
|
selectRadio('summary');
|
||||||
@ -1241,75 +1242,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
</script>
|
</script>
|
||||||
<div class="agent-container">
|
<div class="agent-container">
|
||||||
<!-- Agent Header -->
|
<!-- Agent Header -->
|
||||||
<div class="agent-header">
|
{% include "components/agent_header.html" with agent_title="Data Analyzer" agent_subtitle="Upload your data file and get AI-powered analysis with advanced insights" %}
|
||||||
<div>
|
|
||||||
<h1 class="agent-title">Data Analyzer</h1>
|
|
||||||
<p class="agent-subtitle">Upload your data file and get AI-powered analysis with advanced insights</p>
|
|
||||||
</div>
|
|
||||||
<div class="header-controls">
|
|
||||||
<div class="wallet-card widget-small" style="margin-bottom: 0;">
|
|
||||||
<div class="wallet-header">
|
|
||||||
<h3 class="wallet-title">Your Wallet</h3>
|
|
||||||
<div class="wallet-icon">💳</div>
|
|
||||||
</div>
|
|
||||||
<div class="balance-display">
|
|
||||||
<div class="balance-amount">
|
|
||||||
<span id="walletBalance">{{ user.wallet_balance|floatformat:2 }}</span> AED
|
|
||||||
</div>
|
|
||||||
<div class="balance-label">Available Balance</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Quick Agent Access Panel -->
|
<!-- Quick Agent Access Panel -->
|
||||||
<div class="quick-agents-overlay" id="quickAgentsOverlay" onclick="closeQuickAgents()" aria-hidden="true"></div>
|
{% include "components/quick_agents_panel.html" %}
|
||||||
<div class="quick-agents-panel" id="quickAgentsPanel" role="dialog" aria-labelledby="quickAgentsTitle" aria-hidden="true">
|
|
||||||
<div class="quick-agents-header">
|
|
||||||
<h3 id="quickAgentsTitle">Quick Access to Other Agents</h3>
|
|
||||||
<button class="close-panel" onclick="toggleQuickAgents()" aria-label="Close quick agents panel">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="quick-agents-grid">
|
|
||||||
<a href="/agents/weather-reporter/" class="quick-agent-card">
|
|
||||||
<div class="agent-icon">🌤️</div>
|
|
||||||
<div class="agent-info">
|
|
||||||
<h4>Weather Reporter</h4>
|
|
||||||
<p>Real-time weather info</p>
|
|
||||||
<span class="agent-price">2.0 AED</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="/agents/job-posting-generator/" class="quick-agent-card">
|
|
||||||
<div class="agent-icon">💼</div>
|
|
||||||
<div class="agent-info">
|
|
||||||
<h4>Job Posting Generator</h4>
|
|
||||||
<p>Create professional job posts</p>
|
|
||||||
<span class="agent-price">3.0 AED</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="/agents/social-ads-generator/" class="quick-agent-card">
|
|
||||||
<div class="agent-icon">📱</div>
|
|
||||||
<div class="agent-info">
|
|
||||||
<h4>Social Ads Generator</h4>
|
|
||||||
<p>Engaging social media ads</p>
|
|
||||||
<span class="agent-price">4.0 AED</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="/agents/five-whys-analyzer/" class="quick-agent-card">
|
|
||||||
<div class="agent-icon">🔍</div>
|
|
||||||
<div class="agent-info">
|
|
||||||
<h4>Five Whys Analyzer</h4>
|
|
||||||
<p>Root cause analysis</p>
|
|
||||||
<span class="agent-price">8.0 AED</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="quick-agents-footer">
|
|
||||||
<a href="{% url 'core:marketplace' %}" class="view-all-agents">View All Agents →</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Agent Grid -->
|
<!-- Agent Grid -->
|
||||||
<div class="agent-grid">
|
<div class="agent-grid">
|
||||||
@ -1372,7 +1308,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
<div class="alert alert-error">
|
<div class="alert alert-error">
|
||||||
Insufficient balance! You need 5.00 AED.
|
Insufficient balance! You need 5.00 AED.
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'core:wallet' %}" class="btn btn-primary btn-full">
|
<a href="{% url 'wallet:wallet' %}" class="btn btn-primary btn-full">
|
||||||
💳 Top Up Wallet
|
💳 Top Up Wallet
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -1419,41 +1355,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
<!-- Main Content Grid -->
|
<!-- Main Content Grid -->
|
||||||
<div class="agent-grid">
|
<div class="agent-grid">
|
||||||
<!-- Processing Status -->
|
<!-- Processing Status -->
|
||||||
<div id="processingStatus" class="agent-widget widget-wide processing-status">
|
{% include "components/processing_status.html" %}
|
||||||
<div class="widget-header">
|
|
||||||
<h3 class="widget-title">
|
|
||||||
<span class="widget-icon">⏳</span>
|
|
||||||
Processing Status
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<div class="widget-content" style="text-align: center;">
|
|
||||||
<div class="status-icon">⏳</div>
|
|
||||||
<div class="status-title">Analyzing your data...</div>
|
|
||||||
<div class="status-subtitle" id="statusText">Processing file...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Results Widget -->
|
<!-- Results Widget -->
|
||||||
<div id="resultsContainer" class="agent-widget widget-wide results-container">
|
{% include "components/results_container.html" with results_title="Analysis Results" %}
|
||||||
<div class="widget-header">
|
|
||||||
<h3 class="widget-title">
|
|
||||||
<span class="widget-icon">📊</span>
|
|
||||||
Analysis Results
|
|
||||||
</h3>
|
|
||||||
<span class="status-badge">Success</span>
|
|
||||||
</div>
|
|
||||||
<div class="widget-content">
|
|
||||||
<div id="resultsContent" class="results-content"></div>
|
|
||||||
<div class="results-actions">
|
|
||||||
<button onclick="copyResults()" class="btn btn-primary">
|
|
||||||
📋 Copy Results
|
|
||||||
</button>
|
|
||||||
<button onclick="downloadResults()" class="btn btn-secondary">
|
|
||||||
💾 Download Report
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,952 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block title %}Data Analyzer Agent - NetCop AI Hub{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_css %}
|
|
||||||
<!-- Optimized Font Loading -->
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
|
||||||
|
|
||||||
<!-- External Stylesheets -->
|
|
||||||
<link rel="stylesheet" href="{% static 'css/themes.css' %}">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/agent-base.css' %}">
|
|
||||||
|
|
||||||
<!-- Data Analyzer Specific Utilities -->
|
|
||||||
<script>
|
|
||||||
// Data Analyzer - Self-contained utilities (no shared dependencies)
|
|
||||||
const DataAnalyzerUtils = {
|
|
||||||
/**
|
|
||||||
* Update wallet balance display - Data Analyzer specific
|
|
||||||
*/
|
|
||||||
updateWalletBalance(newBalance) {
|
|
||||||
// Update header balance (anchor tag with emoji)
|
|
||||||
const headerBalance = document.querySelector('a[data-wallet-balance]');
|
|
||||||
if (headerBalance) {
|
|
||||||
headerBalance.textContent = `💰 ${newBalance.toFixed(2)} AED`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update page balance (div without emoji)
|
|
||||||
const pageBalance = document.querySelector('div[data-wallet-balance]');
|
|
||||||
if (pageBalance) {
|
|
||||||
pageBalance.textContent = `${newBalance.toFixed(2)} AED`;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.currentWalletBalance = newBalance;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show toast notification with duplicate prevention
|
|
||||||
*/
|
|
||||||
showToast(message, type = 'info') {
|
|
||||||
// Prevent duplicate toasts
|
|
||||||
const existingToast = document.querySelector('.data-analyzer-toast');
|
|
||||||
if (existingToast) {
|
|
||||||
existingToast.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
const toast = document.createElement('div');
|
|
||||||
toast.className = 'data-analyzer-toast';
|
|
||||||
toast.style.cssText = `
|
|
||||||
position: fixed;
|
|
||||||
top: 16px;
|
|
||||||
right: 16px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: white;
|
|
||||||
font-size: 13px;
|
|
||||||
z-index: 1000;
|
|
||||||
max-width: 300px;
|
|
||||||
font-weight: 500;
|
|
||||||
${type === 'success' ? 'background: #10b981;' : 'background: #ef4444;'}
|
|
||||||
`;
|
|
||||||
toast.textContent = message;
|
|
||||||
document.body.appendChild(toast);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
if (toast.parentNode) {
|
|
||||||
toast.remove();
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate text for copy/download functionality
|
|
||||||
*/
|
|
||||||
generateTextForExport(contentElementId) {
|
|
||||||
const content = document.getElementById(contentElementId);
|
|
||||||
if (content) {
|
|
||||||
return content.innerText || content.textContent || '';
|
|
||||||
}
|
|
||||||
return 'No content available';
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy content to clipboard
|
|
||||||
*/
|
|
||||||
copyToClipboard(text, successMessage = 'Content copied to clipboard!') {
|
|
||||||
navigator.clipboard.writeText(text).then(() => {
|
|
||||||
this.showToast(`📋 ${successMessage}`, 'success');
|
|
||||||
}).catch(() => {
|
|
||||||
this.showToast('Failed to copy content', 'error');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Download content as text file
|
|
||||||
*/
|
|
||||||
downloadAsFile(text, filename, successMessage = 'File downloaded!') {
|
|
||||||
const blob = new Blob([text], { type: 'text/plain' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = filename || `content-${Date.now()}.txt`;
|
|
||||||
a.click();
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
this.showToast(`💾 ${successMessage}`, 'success');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// For backward compatibility, create AgentUtils alias
|
|
||||||
const AgentUtils = DataAnalyzerUtils;
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
.main-container {
|
|
||||||
max-width: none;
|
|
||||||
padding: 0;
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Page background */
|
|
||||||
.data-analyzer-page {
|
|
||||||
background: var(--gradient-hero);
|
|
||||||
min-height: calc(100vh - 80px);
|
|
||||||
padding: clamp(20px, 5vw, 40px);
|
|
||||||
width: 100vw;
|
|
||||||
margin-left: calc(-50vw + 50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 clamp(16px, 4vw, 24px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main grid layout */
|
|
||||||
.main-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(min(350px, 100%), 1fr));
|
|
||||||
gap: clamp(16px, 4vw, 24px);
|
|
||||||
align-items: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Card styles with glassmorphism */
|
|
||||||
.card {
|
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
border-radius: clamp(12px, 3vw, 16px);
|
|
||||||
padding: clamp(16px, 4vw, 24px);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
|
||||||
margin-bottom: clamp(16px, 4vw, 24px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: clamp(16px, 4vw, 18px);
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-primary);
|
|
||||||
margin-bottom: clamp(12px, 3vw, 16px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form inputs */
|
|
||||||
.form-input {
|
|
||||||
width: 100%;
|
|
||||||
padding: clamp(12px, 3vw, 16px) clamp(16px, 4vw, 20px);
|
|
||||||
border: 2px solid var(--border-medium);
|
|
||||||
border-radius: clamp(8px, 2vw, 12px);
|
|
||||||
font-size: clamp(14px, 3.5vw, 16px);
|
|
||||||
transition: border-color 0.2s ease;
|
|
||||||
min-height: 48px;
|
|
||||||
margin-bottom: clamp(12px, 3vw, 16px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--success-green);
|
|
||||||
}
|
|
||||||
|
|
||||||
.help-text {
|
|
||||||
font-size: clamp(12px, 3vw, 14px);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* File upload zone */
|
|
||||||
.file-upload-zone {
|
|
||||||
border: 2px dashed var(--border-medium);
|
|
||||||
border-radius: clamp(12px, 3vw, 16px);
|
|
||||||
padding: clamp(24px, 6vw, 40px);
|
|
||||||
text-align: center;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
background: var(--background-light);
|
|
||||||
cursor: pointer;
|
|
||||||
margin-bottom: clamp(12px, 3vw, 16px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-upload-zone.dragover {
|
|
||||||
border-color: var(--success-green);
|
|
||||||
background: rgba(16, 185, 129, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-upload-zone:hover {
|
|
||||||
border-color: var(--success-green);
|
|
||||||
background: rgba(16, 185, 129, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-preview {
|
|
||||||
background: rgba(16, 185, 129, 0.1);
|
|
||||||
border: 1px solid rgba(16, 185, 129, 0.5);
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
margin-top: 12px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Radio button grids */
|
|
||||||
.radio-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
|
|
||||||
gap: clamp(8px, 2vw, 12px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-option {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: clamp(8px, 2vw, 12px);
|
|
||||||
padding: clamp(12px, 3vw, 16px);
|
|
||||||
border: 2px solid var(--border-light);
|
|
||||||
border-radius: clamp(8px, 2vw, 12px);
|
|
||||||
cursor: pointer;
|
|
||||||
background: white;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
min-height: 44px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-option.selected {
|
|
||||||
border-color: var(--success-green);
|
|
||||||
background: rgba(16, 185, 129, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-option input[type="radio"] {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Processing status */
|
|
||||||
.processing-status {
|
|
||||||
padding: clamp(16px, 4vw, 20px);
|
|
||||||
background: rgba(16, 185, 129, 0.1);
|
|
||||||
border: 1px solid var(--success-green);
|
|
||||||
border-radius: clamp(8px, 2vw, 12px);
|
|
||||||
color: var(--success-dark);
|
|
||||||
font-weight: 600;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: clamp(16px, 4vw, 24px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Results card */
|
|
||||||
.results-card {
|
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
border-radius: 16px;
|
|
||||||
padding: 24px;
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.results-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.results-content {
|
|
||||||
background: var(--background-page);
|
|
||||||
border: 1px solid var(--border-light);
|
|
||||||
border-radius: 12px;
|
|
||||||
padding: 24px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
white-space: pre-line;
|
|
||||||
line-height: 1.7;
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
margin-top: 20px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Button styles */
|
|
||||||
.btn {
|
|
||||||
padding: clamp(12px, 3vw, 16px) clamp(20px, 5vw, 32px);
|
|
||||||
border: none;
|
|
||||||
border-radius: clamp(8px, 2vw, 12px);
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: clamp(14px, 3.5vw, 16px);
|
|
||||||
min-height: 48px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
transition: transform 0.1s ease;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background: var(--gradient-success);
|
|
||||||
color: white;
|
|
||||||
flex: 1;
|
|
||||||
min-width: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary {
|
|
||||||
background: var(--background-subtle);
|
|
||||||
color: var(--text-primary);
|
|
||||||
border: 2px solid var(--border-medium);
|
|
||||||
flex: 1;
|
|
||||||
min-width: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:disabled {
|
|
||||||
background: var(--border-strong);
|
|
||||||
color: white;
|
|
||||||
cursor: not-allowed;
|
|
||||||
transform: none;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Wallet sidebar */
|
|
||||||
.wallet-section {
|
|
||||||
position: sticky;
|
|
||||||
top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wallet-balance {
|
|
||||||
font-size: clamp(24px, 6vw, 28px);
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--text-primary);
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.balance-label {
|
|
||||||
font-size: clamp(14px, 3.5vw, 16px);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
margin-bottom: clamp(16px, 4vw, 20px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.process-btn {
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.usage-info {
|
|
||||||
padding: 16px;
|
|
||||||
background: rgba(16, 185, 129, 0.1);
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 1px solid rgba(16, 185, 129, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.usage-info h4 {
|
|
||||||
margin: 0 0 8px 0;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--success-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.usage-info ul {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--text-primary);
|
|
||||||
line-height: 1.4;
|
|
||||||
list-style: none;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.usage-info li {
|
|
||||||
margin: 4px 0;
|
|
||||||
padding-left: 16px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.usage-info li::before {
|
|
||||||
content: "•";
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
color: var(--success-green);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.main-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-buttons {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="agent-page theme-professional">
|
|
||||||
<div class="agent-container">
|
|
||||||
<!-- Main Content -->
|
|
||||||
<div>
|
|
||||||
<form method="POST" id="dataAnalyzerForm">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<!-- File Upload Section -->
|
|
||||||
<div class="card">
|
|
||||||
<h3 class="section-title">📁 Upload Your Data File</h3>
|
|
||||||
|
|
||||||
<div class="file-upload-zone" id="fileUploadZone" onclick="document.getElementById('fileInput').click()">
|
|
||||||
<div style="font-size: clamp(32px, 8vw, 48px); margin-bottom: 12px;">📊</div>
|
|
||||||
<div style="font-size: clamp(16px, 4vw, 18px); font-weight: 600; color: var(--text-primary); margin-bottom: 8px;">
|
|
||||||
Choose or drag your data file here
|
|
||||||
</div>
|
|
||||||
<div style="font-size: clamp(14px, 3.5vw, 16px); color: var(--text-secondary);">
|
|
||||||
Supports PDF, CSV, Excel files (up to 10MB)
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
id="fileInput"
|
|
||||||
accept=".pdf,.csv,.xlsx,.xls"
|
|
||||||
style="display: none;"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div id="filePreview" class="file-preview" style="display: none;">
|
|
||||||
<div style="font-size: 24px;">📄</div>
|
|
||||||
<div style="flex: 1;">
|
|
||||||
<div style="font-weight: 600; color: var(--text-primary);" id="fileName"></div>
|
|
||||||
<div style="font-size: 14px; color: var(--text-secondary);" id="fileSize"></div>
|
|
||||||
</div>
|
|
||||||
<button type="button" onclick="removeFile()" style="
|
|
||||||
background: var(--error-red);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 4px 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
">
|
|
||||||
Remove
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Analysis Type Selection -->
|
|
||||||
<div class="card">
|
|
||||||
<h3 class="section-title">🔍 Analysis Type</h3>
|
|
||||||
|
|
||||||
<div class="radio-grid">
|
|
||||||
<label class="radio-option selected" data-value="summary">
|
|
||||||
<input type="radio" name="analysisType" value="summary" checked />
|
|
||||||
<div style="font-size: clamp(16px, 4vw, 20px);">📋</div>
|
|
||||||
<div>
|
|
||||||
<div style="font-weight: 600; margin-bottom: clamp(2px, 1vw, 4px); font-size: clamp(14px, 3.5vw, 16px);">
|
|
||||||
Summary Analysis
|
|
||||||
</div>
|
|
||||||
<div style="font-size: clamp(12px, 3vw, 14px); color: var(--text-secondary);">
|
|
||||||
Quick overview and key insights
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="radio-option" data-value="detailed">
|
|
||||||
<input type="radio" name="analysisType" value="detailed" />
|
|
||||||
<div style="font-size: clamp(16px, 4vw, 20px);">📈</div>
|
|
||||||
<div>
|
|
||||||
<div style="font-weight: 600; margin-bottom: clamp(2px, 1vw, 4px); font-size: clamp(14px, 3.5vw, 16px);">
|
|
||||||
Detailed Analysis
|
|
||||||
</div>
|
|
||||||
<div style="font-size: clamp(12px, 3vw, 14px); color: var(--text-secondary);">
|
|
||||||
Comprehensive statistical analysis
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="radio-option" data-value="statistical">
|
|
||||||
<input type="radio" name="analysisType" value="statistical" />
|
|
||||||
<div style="font-size: clamp(16px, 4vw, 20px);">📊</div>
|
|
||||||
<div>
|
|
||||||
<div style="font-weight: 600; margin-bottom: clamp(2px, 1vw, 4px); font-size: clamp(14px, 3.5vw, 16px);">
|
|
||||||
Statistical Analysis
|
|
||||||
</div>
|
|
||||||
<div style="font-size: clamp(12px, 3vw, 14px); color: var(--text-secondary);">
|
|
||||||
Advanced statistics and trends
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- Processing Status -->
|
|
||||||
<div id="processingStatus" class="processing-status" style="display: none;">
|
|
||||||
<div style="font-size: clamp(16px, 4vw, 18px); margin-bottom: 8px;">
|
|
||||||
⏳ Processing...
|
|
||||||
</div>
|
|
||||||
<div style="font-size: clamp(14px, 3.5vw, 16px);" id="statusText">
|
|
||||||
Analyzing your data file...
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Results -->
|
|
||||||
<div id="analysisResults" class="results-card" style="display: none;">
|
|
||||||
<div class="results-header">
|
|
||||||
<div style="font-size: 24px;">✅</div>
|
|
||||||
<h3 style="font-size: 20px; font-weight: 600; color: var(--text-primary); margin: 0;">
|
|
||||||
Data Analysis Results
|
|
||||||
</h3>
|
|
||||||
<div style="
|
|
||||||
background: var(--success-green);
|
|
||||||
color: white;
|
|
||||||
padding: 6px 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-left: auto;
|
|
||||||
">
|
|
||||||
✅ Complete
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="results-content" id="analysisContent">
|
|
||||||
<!-- Analysis data will be displayed here -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="action-buttons">
|
|
||||||
<button onclick="copyAnalysisReport()" class="btn btn-primary">
|
|
||||||
📋 Copy Report
|
|
||||||
</button>
|
|
||||||
<button onclick="downloadAnalysisReport()" class="btn btn-secondary">
|
|
||||||
💾 Download Report
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Wallet Sidebar -->
|
|
||||||
<div class="wallet-section">
|
|
||||||
<div class="card">
|
|
||||||
<h3 class="section-title">💳 Your Wallet</h3>
|
|
||||||
|
|
||||||
<div class="wallet-balance" data-wallet-balance>{{ user.wallet_balance|floatformat:2 }} AED</div>
|
|
||||||
<div class="balance-label">Available Balance</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
form="dataAnalyzerForm"
|
|
||||||
class="btn btn-primary process-btn"
|
|
||||||
id="processButton"
|
|
||||||
>
|
|
||||||
📊 Analyze Data (5.00 AED)
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="usage-info">
|
|
||||||
<h4>💡 How it works</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Upload PDF, CSV, or Excel files</li>
|
|
||||||
<li>Choose your analysis depth</li>
|
|
||||||
<li>Get AI-powered insights</li>
|
|
||||||
<li>Download comprehensive reports</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_js %}
|
|
||||||
<script>
|
|
||||||
let selectedFile = null;
|
|
||||||
|
|
||||||
// File upload handling
|
|
||||||
const fileInput = document.getElementById('fileInput');
|
|
||||||
const fileUploadZone = document.getElementById('fileUploadZone');
|
|
||||||
const filePreview = document.getElementById('filePreview');
|
|
||||||
|
|
||||||
// Handle file input change
|
|
||||||
fileInput.addEventListener('change', function(e) {
|
|
||||||
if (e.target.files.length > 0) {
|
|
||||||
selectedFile = e.target.files[0];
|
|
||||||
showFilePreview(selectedFile);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Drag and drop functionality
|
|
||||||
fileUploadZone.addEventListener('dragover', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
fileUploadZone.classList.add('dragover');
|
|
||||||
});
|
|
||||||
|
|
||||||
fileUploadZone.addEventListener('dragleave', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
fileUploadZone.classList.remove('dragover');
|
|
||||||
});
|
|
||||||
|
|
||||||
fileUploadZone.addEventListener('drop', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
fileUploadZone.classList.remove('dragover');
|
|
||||||
|
|
||||||
const files = e.dataTransfer.files;
|
|
||||||
if (files.length > 0) {
|
|
||||||
const file = files[0];
|
|
||||||
if (isValidFile(file)) {
|
|
||||||
selectedFile = file;
|
|
||||||
fileInput.files = files;
|
|
||||||
showFilePreview(file);
|
|
||||||
} else {
|
|
||||||
AgentUtils.showToast('Please select a valid data file (PDF, CSV, Excel)', 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show file preview
|
|
||||||
function showFilePreview(file) {
|
|
||||||
document.getElementById('fileName').textContent = file.name;
|
|
||||||
document.getElementById('fileSize').textContent = formatFileSize(file.size);
|
|
||||||
filePreview.style.display = 'flex';
|
|
||||||
fileUploadZone.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove file
|
|
||||||
function removeFile() {
|
|
||||||
selectedFile = null;
|
|
||||||
fileInput.value = '';
|
|
||||||
filePreview.style.display = 'none';
|
|
||||||
fileUploadZone.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate file type
|
|
||||||
function isValidFile(file) {
|
|
||||||
const validTypes = [
|
|
||||||
'application/pdf',
|
|
||||||
'text/csv',
|
|
||||||
'application/vnd.ms-excel',
|
|
||||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
||||||
];
|
|
||||||
return validTypes.includes(file.type) || file.name.match(/\.(pdf|csv|xlsx|xls)$/i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format file size
|
|
||||||
function formatFileSize(bytes) {
|
|
||||||
if (bytes === 0) return '0 Bytes';
|
|
||||||
const k = 1024;
|
|
||||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Radio button handling
|
|
||||||
document.querySelectorAll('.radio-option').forEach(option => {
|
|
||||||
option.addEventListener('click', function() {
|
|
||||||
document.querySelectorAll('.radio-option').forEach(opt => opt.classList.remove('selected'));
|
|
||||||
this.classList.add('selected');
|
|
||||||
this.querySelector('input[type="radio"]').checked = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle form submission
|
|
||||||
document.getElementById('dataAnalyzerForm').addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!selectedFile) {
|
|
||||||
AgentUtils.showToast('Please select a data file', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check user authentication
|
|
||||||
{% if not user.is_authenticated %}
|
|
||||||
window.location.href = "{% url 'authentication:login' %}";
|
|
||||||
return;
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
// Check wallet balance
|
|
||||||
const balance = {{ user.wallet_balance|default:0 }};
|
|
||||||
if (balance < 5.00) {
|
|
||||||
AgentUtils.showToast('Insufficient balance! You need 5.00 AED.', 'error');
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.href = "{% url 'core:wallet' %}";
|
|
||||||
}, 2000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show processing status
|
|
||||||
document.getElementById('processingStatus').style.display = 'block';
|
|
||||||
document.getElementById('processButton').disabled = true;
|
|
||||||
document.getElementById('processButton').innerHTML = '⏳ Processing...';
|
|
||||||
document.getElementById('analysisResults').style.display = 'none';
|
|
||||||
|
|
||||||
// Processing steps for user feedback
|
|
||||||
const steps = [
|
|
||||||
'Reading file structure...',
|
|
||||||
'Extracting data patterns...',
|
|
||||||
'Performing statistical analysis...',
|
|
||||||
'Generating insights...',
|
|
||||||
'Finalizing report...'
|
|
||||||
];
|
|
||||||
|
|
||||||
let currentStep = 0;
|
|
||||||
const stepInterval = setInterval(() => {
|
|
||||||
if (currentStep < steps.length) {
|
|
||||||
document.getElementById('statusText').textContent = steps[currentStep];
|
|
||||||
currentStep++;
|
|
||||||
} else {
|
|
||||||
clearInterval(stepInterval);
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
// Submit form data to backend
|
|
||||||
const formData = new FormData(this);
|
|
||||||
if (selectedFile) {
|
|
||||||
formData.append('file', selectedFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch(window.location.href, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
headers: {
|
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(result => {
|
|
||||||
clearInterval(stepInterval);
|
|
||||||
if (result.success && result.request_id) {
|
|
||||||
// Start polling for results
|
|
||||||
pollForResults(result.request_id);
|
|
||||||
} else {
|
|
||||||
// Handle immediate response
|
|
||||||
document.getElementById('processingStatus').style.display = 'none';
|
|
||||||
document.getElementById('processButton').disabled = false;
|
|
||||||
document.getElementById('processButton').innerHTML = '📊 Analyze Data (5.00 AED)';
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
AgentUtils.showToast(`❌ ${result.error}`, 'error');
|
|
||||||
} else {
|
|
||||||
displayResults(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
clearInterval(stepInterval);
|
|
||||||
console.error('Error:', error);
|
|
||||||
document.getElementById('processingStatus').style.display = 'none';
|
|
||||||
document.getElementById('processButton').disabled = false;
|
|
||||||
document.getElementById('processButton').innerHTML = '📊 Analyze Data (5.00 AED)';
|
|
||||||
AgentUtils.showToast('❌ Network error - please try again', 'error');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Display analysis results with custom Data Analyzer formatting
|
|
||||||
function displayResults(result) {
|
|
||||||
const resultsContainer = document.getElementById('analysisResults');
|
|
||||||
const contentElement = document.getElementById('analysisContent');
|
|
||||||
|
|
||||||
if (!resultsContainer || !contentElement) {
|
|
||||||
console.error('Results elements not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update wallet balance if provided
|
|
||||||
if (result.wallet_balance !== undefined) {
|
|
||||||
AgentUtils.updateWalletBalance(result.wallet_balance);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle errors
|
|
||||||
if (result.error) {
|
|
||||||
AgentUtils.showToast(`❌ ${result.error}`, 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract content with priority: report_text > insights_summary > raw_response.analysis
|
|
||||||
let content = '';
|
|
||||||
|
|
||||||
if (result.report_text && typeof result.report_text === 'string') {
|
|
||||||
content = result.report_text;
|
|
||||||
} else if (result.insights_summary && typeof result.insights_summary === 'string') {
|
|
||||||
content = result.insights_summary;
|
|
||||||
} else if (result.raw_response && result.raw_response.analysis && typeof result.raw_response.analysis === 'string') {
|
|
||||||
content = result.raw_response.analysis;
|
|
||||||
} else {
|
|
||||||
content = 'Data analysis completed successfully!';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse markdown content to HTML
|
|
||||||
const formattedContent = parseMarkdownToHTML(content);
|
|
||||||
|
|
||||||
// Update content
|
|
||||||
contentElement.innerHTML = formattedContent;
|
|
||||||
|
|
||||||
// Show results
|
|
||||||
resultsContainer.style.display = 'block';
|
|
||||||
resultsContainer.scrollIntoView({ behavior: 'smooth' });
|
|
||||||
|
|
||||||
// Show success message
|
|
||||||
const successMessage = result.success ? '✅ Data analysis completed and payment processed!' : '✅ Data analysis completed!';
|
|
||||||
AgentUtils.showToast(successMessage, 'success');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enhanced markdown parser for Data Analyzer results
|
|
||||||
function parseMarkdownToHTML(markdown) {
|
|
||||||
if (!markdown || typeof markdown !== 'string') {
|
|
||||||
return '<p>No content available.</p>';
|
|
||||||
}
|
|
||||||
|
|
||||||
let html = markdown;
|
|
||||||
|
|
||||||
// Convert headers (### Header, ## Header, # Header)
|
|
||||||
html = html.replace(/^### (.*$)/gm, '<h3 style="font-size: 18px; font-weight: 600; color: var(--text-primary); margin: 20px 0 12px 0; border-bottom: 2px solid var(--success-green); padding-bottom: 8px;">$1</h3>');
|
|
||||||
html = html.replace(/^## (.*$)/gm, '<h2 style="font-size: 20px; font-weight: 600; color: var(--text-primary); margin: 24px 0 16px 0; border-bottom: 2px solid var(--success-green); padding-bottom: 8px;">$1</h2>');
|
|
||||||
html = html.replace(/^# (.*$)/gm, '<h1 style="font-size: 22px; font-weight: 600; color: var(--text-primary); margin: 28px 0 18px 0; border-bottom: 2px solid var(--success-green); padding-bottom: 10px;">$1</h1>');
|
|
||||||
|
|
||||||
// Convert bold text (**text** or __text__)
|
|
||||||
html = html.replace(/\*\*(.*?)\*\*/g, '<strong style="font-weight: 600; color: var(--text-primary);">$1</strong>');
|
|
||||||
html = html.replace(/__(.*?)__/g, '<strong style="font-weight: 600; color: var(--text-primary);">$1</strong>');
|
|
||||||
|
|
||||||
// Convert italic text (*text* or _text_)
|
|
||||||
html = html.replace(/\*(.*?)\*/g, '<em style="font-style: italic; color: var(--text-secondary);">$1</em>');
|
|
||||||
html = html.replace(/_(.*?)_/g, '<em style="font-style: italic; color: var(--text-secondary);">$1</em>');
|
|
||||||
|
|
||||||
// Convert bullet points (- item or * item)
|
|
||||||
html = html.replace(/^[\s]*[-\*]\s+(.*)$/gm, '<li style="margin: 8px 0; padding-left: 8px; color: var(--text-primary);">$1</li>');
|
|
||||||
|
|
||||||
// Wrap consecutive list items in ul tags
|
|
||||||
html = html.replace(/(<li[^>]*>.*?<\/li>\s*)+/gs, function(match) {
|
|
||||||
return `<ul style="margin: 16px 0; padding-left: 20px; list-style-type: disc; color: var(--success-green);">${match}</ul>`;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Convert numbered lists (1. item, 2. item)
|
|
||||||
html = html.replace(/^\s*\d+\.\s+(.*)$/gm, '<li style="margin: 8px 0; padding-left: 8px; color: var(--text-primary);">$1</li>');
|
|
||||||
|
|
||||||
// Wrap consecutive numbered list items in ol tags
|
|
||||||
html = html.replace(/(<li[^>]*>.*?<\/li>\s*)+/gs, function(match) {
|
|
||||||
if (match.includes('ul style')) return match; // Skip if already wrapped in ul
|
|
||||||
return `<ol style="margin: 16px 0; padding-left: 20px; list-style-type: decimal; color: var(--success-green);">${match}</ol>`;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Convert line breaks to paragraphs
|
|
||||||
const paragraphs = html.split(/\n\s*\n/);
|
|
||||||
html = paragraphs.map(p => {
|
|
||||||
const trimmed = p.trim();
|
|
||||||
if (trimmed === '') return '';
|
|
||||||
|
|
||||||
// Skip if already wrapped in HTML tags
|
|
||||||
if (trimmed.startsWith('<h') || trimmed.startsWith('<ul') || trimmed.startsWith('<ol') || trimmed.startsWith('<li')) {
|
|
||||||
return trimmed;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `<p style="margin: 12px 0; line-height: 1.6; color: var(--text-primary);">${trimmed}</p>`;
|
|
||||||
}).filter(p => p !== '').join('');
|
|
||||||
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track polling and results to prevent duplicates
|
|
||||||
let resultsDisplayed = false;
|
|
||||||
let currentPollInterval = null;
|
|
||||||
|
|
||||||
// Poll for results
|
|
||||||
function pollForResults(requestId) {
|
|
||||||
let pollCount = 0;
|
|
||||||
const maxPolls = 60; // 60 seconds maximum for data analysis
|
|
||||||
resultsDisplayed = false; // Reset flag
|
|
||||||
|
|
||||||
// Clear any existing polling
|
|
||||||
if (currentPollInterval) {
|
|
||||||
clearInterval(currentPollInterval);
|
|
||||||
currentPollInterval = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPollInterval = setInterval(() => {
|
|
||||||
pollCount++;
|
|
||||||
|
|
||||||
fetch(`/agents/data-analyzer/status/${requestId}/`)
|
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP ${response.status}`);
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(result => {
|
|
||||||
if (result.status === 'completed' || result.status === 'failed') {
|
|
||||||
// Stop polling immediately
|
|
||||||
clearInterval(currentPollInterval);
|
|
||||||
currentPollInterval = null;
|
|
||||||
|
|
||||||
// Reset UI
|
|
||||||
document.getElementById('processingStatus').style.display = 'none';
|
|
||||||
document.getElementById('processButton').disabled = false;
|
|
||||||
document.getElementById('processButton').innerHTML = '📊 Analyze Data (5.00 AED)';
|
|
||||||
|
|
||||||
// Display results only once
|
|
||||||
if (!resultsDisplayed) {
|
|
||||||
resultsDisplayed = true;
|
|
||||||
displayResults(result);
|
|
||||||
}
|
|
||||||
} else if (pollCount >= maxPolls) {
|
|
||||||
clearInterval(currentPollInterval);
|
|
||||||
currentPollInterval = null;
|
|
||||||
document.getElementById('processingStatus').style.display = 'none';
|
|
||||||
document.getElementById('processButton').disabled = false;
|
|
||||||
document.getElementById('processButton').innerHTML = '📊 Analyze Data (5.00 AED)';
|
|
||||||
AgentUtils.showToast('❌ Processing timeout - please try again', 'error');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error polling results:', error);
|
|
||||||
clearInterval(currentPollInterval);
|
|
||||||
currentPollInterval = null;
|
|
||||||
document.getElementById('processingStatus').style.display = 'none';
|
|
||||||
document.getElementById('processButton').disabled = false;
|
|
||||||
document.getElementById('processButton').innerHTML = '📊 Analyze Data (5.00 AED)';
|
|
||||||
AgentUtils.showToast('❌ Network error during processing - please try again', 'error');
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function copyAnalysisReport() {
|
|
||||||
const reportText = AgentUtils.generateTextForExport('analysisContent');
|
|
||||||
AgentUtils.copyToClipboard(reportText, 'Analysis report copied to clipboard!');
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadAnalysisReport() {
|
|
||||||
const reportText = AgentUtils.generateTextForExport('analysisContent');
|
|
||||||
AgentUtils.downloadAsFile(reportText, `data-analysis-report-${Date.now()}.txt`, 'Analysis report downloaded!');
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,744 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block title %}Data Analyzer Agent - NetCop AI Hub{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_css %}
|
|
||||||
<!-- Optimized Font Loading -->
|
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
|
||||||
|
|
||||||
<!-- External Stylesheets -->
|
|
||||||
<link rel="stylesheet" href="{% static 'css/themes.css' %}">
|
|
||||||
<link rel="stylesheet" href="{% static 'css/agent-base.css' %}">
|
|
||||||
|
|
||||||
<!-- Data Analyzer Specific Utilities -->
|
|
||||||
<script>
|
|
||||||
// Data Analyzer - Self-contained utilities (no shared dependencies)
|
|
||||||
const DataAnalyzerUtils = {
|
|
||||||
/**
|
|
||||||
* Update wallet balance display - Data Analyzer specific
|
|
||||||
*/
|
|
||||||
updateWalletBalance(newBalance) {
|
|
||||||
// Update header balance (anchor tag with emoji)
|
|
||||||
const headerBalance = document.querySelector('a[data-wallet-balance]');
|
|
||||||
if (headerBalance) {
|
|
||||||
headerBalance.textContent = `💰 ${newBalance.toFixed(2)} AED`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update page balance (div without emoji)
|
|
||||||
const pageBalance = document.querySelector('div[data-wallet-balance]');
|
|
||||||
if (pageBalance) {
|
|
||||||
pageBalance.textContent = `${newBalance.toFixed(2)} AED`;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.currentWalletBalance = newBalance;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show toast notification with duplicate prevention
|
|
||||||
*/
|
|
||||||
showToast(message, type = 'info') {
|
|
||||||
// Prevent duplicate toasts
|
|
||||||
const existingToast = document.querySelector('.data-analyzer-toast');
|
|
||||||
if (existingToast) {
|
|
||||||
existingToast.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
const toast = document.createElement('div');
|
|
||||||
toast.className = 'data-analyzer-toast';
|
|
||||||
toast.style.cssText = `
|
|
||||||
position: fixed;
|
|
||||||
top: 16px;
|
|
||||||
right: 16px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
color: white;
|
|
||||||
font-size: 13px;
|
|
||||||
z-index: 1000;
|
|
||||||
max-width: 300px;
|
|
||||||
font-weight: 500;
|
|
||||||
${type === 'success' ? 'background: #10b981;' : 'background: #ef4444;'}
|
|
||||||
`;
|
|
||||||
toast.textContent = message;
|
|
||||||
document.body.appendChild(toast);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
if (toast.parentNode) {
|
|
||||||
toast.remove();
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate text for copy/download functionality
|
|
||||||
*/
|
|
||||||
generateTextForExport(contentElementId) {
|
|
||||||
const content = document.getElementById(contentElementId);
|
|
||||||
if (content) {
|
|
||||||
return content.innerText || content.textContent || '';
|
|
||||||
}
|
|
||||||
return 'No content available';
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy content to clipboard
|
|
||||||
*/
|
|
||||||
copyToClipboard(text, successMessage = 'Content copied to clipboard!') {
|
|
||||||
navigator.clipboard.writeText(text).then(() => {
|
|
||||||
this.showToast(`📋 ${successMessage}`, 'success');
|
|
||||||
}).catch(() => {
|
|
||||||
this.showToast('Failed to copy content', 'error');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Download content as text file
|
|
||||||
*/
|
|
||||||
downloadAsFile(text, filename, successMessage = 'File downloaded!') {
|
|
||||||
const blob = new Blob([text], { type: 'text/plain' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = filename || `content-${Date.now()}.txt`;
|
|
||||||
a.click();
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
this.showToast(`💾 ${successMessage}`, 'success');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// For backward compatibility, create AgentUtils alias
|
|
||||||
const AgentUtils = DataAnalyzerUtils;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/* Data Analyzer specific styles only */
|
|
||||||
.file-upload-zone {
|
|
||||||
border: 2px dashed var(--agent-border);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
padding: var(--space-xl);
|
|
||||||
text-align: center;
|
|
||||||
transition: all var(--transition);
|
|
||||||
background: var(--agent-card-bg);
|
|
||||||
cursor: pointer;
|
|
||||||
margin-bottom: var(--space-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-upload-zone.dragover,
|
|
||||||
.file-upload-zone:hover {
|
|
||||||
border-color: var(--agent-primary);
|
|
||||||
background: var(--hover-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-preview {
|
|
||||||
background: var(--agent-card-bg);
|
|
||||||
border: 1px solid var(--agent-border);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
padding: var(--space-md);
|
|
||||||
margin-top: var(--space-md);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
gap: var(--space-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-option {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--space-md);
|
|
||||||
padding: var(--space-lg);
|
|
||||||
border: 1px solid var(--agent-border);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
cursor: pointer;
|
|
||||||
background: white;
|
|
||||||
transition: all var(--transition);
|
|
||||||
min-height: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-option.selected {
|
|
||||||
border-color: var(--agent-primary);
|
|
||||||
background: var(--agent-card-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-option:hover {
|
|
||||||
border-color: var(--agent-primary);
|
|
||||||
background: var(--hover-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-option input[type="radio"] {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.processing-status {
|
|
||||||
display: none;
|
|
||||||
text-align: center;
|
|
||||||
padding: var(--space-lg);
|
|
||||||
margin: var(--space-lg) 0;
|
|
||||||
background: var(--agent-card-bg);
|
|
||||||
border: 1px solid var(--agent-border);
|
|
||||||
border-radius: var(--radius);
|
|
||||||
}
|
|
||||||
|
|
||||||
.results-card {
|
|
||||||
display: none;
|
|
||||||
margin-top: var(--space-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure grid layout works */
|
|
||||||
div.agent-page div.agent-container {
|
|
||||||
display: grid !important;
|
|
||||||
grid-template-columns: 1fr 350px !important;
|
|
||||||
gap: 24px !important;
|
|
||||||
max-width: 1280px !important;
|
|
||||||
margin: 0 auto !important;
|
|
||||||
align-items: start !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
div.agent-page div.agent-container {
|
|
||||||
grid-template-columns: 1fr !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="agent-page theme-professional">
|
|
||||||
<div class="agent-container">
|
|
||||||
<!-- Messages -->
|
|
||||||
{% if messages %}
|
|
||||||
{% for message in messages %}
|
|
||||||
<div class="{% if message.tags == 'error' %}error-message{% else %}success-message{% endif %}" style="grid-column: 1 / -1;">
|
|
||||||
{{ message }}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
|
||||||
<div>
|
|
||||||
<form method="POST" id="dataAnalyzerForm">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<!-- File Upload Section -->
|
|
||||||
<div class="card">
|
|
||||||
<h3 class="section-title">📁 Upload Your Data File</h3>
|
|
||||||
|
|
||||||
<div class="file-upload-zone" id="fileUploadZone" onclick="document.getElementById('fileInput').click()">
|
|
||||||
<div style="font-size: 48px; margin-bottom: 12px;">📊</div>
|
|
||||||
<div style="font-size: var(--text-lg); font-weight: 600; color: var(--text-color); margin-bottom: 8px;">
|
|
||||||
Choose or drag your data file here
|
|
||||||
</div>
|
|
||||||
<div style="font-size: var(--text-base); color: var(--accent-color);">
|
|
||||||
Supports PDF, CSV, Excel files (up to 10MB)
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
id="fileInput"
|
|
||||||
accept=".pdf,.csv,.xlsx,.xls"
|
|
||||||
style="display: none;"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div id="filePreview" class="file-preview" style="display: none;">
|
|
||||||
<div style="font-size: 24px;">📄</div>
|
|
||||||
<div style="flex: 1;">
|
|
||||||
<div style="font-weight: 600; color: var(--text-primary);" id="fileName"></div>
|
|
||||||
<div style="font-size: 14px; color: var(--text-secondary);" id="fileSize"></div>
|
|
||||||
</div>
|
|
||||||
<button type="button" onclick="removeFile()" class="btn btn-secondary" style="padding: 4px 8px; font-size: 12px;">
|
|
||||||
Remove
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Analysis Type Selection -->
|
|
||||||
<div class="card">
|
|
||||||
<h3 class="section-title">🔍 Analysis Type</h3>
|
|
||||||
|
|
||||||
<div class="radio-grid">
|
|
||||||
<label class="radio-option selected" data-value="summary">
|
|
||||||
<input type="radio" name="analysisType" value="summary" checked />
|
|
||||||
<div style="font-size: 20px;">📋</div>
|
|
||||||
<div>
|
|
||||||
<div style="font-weight: 600; margin-bottom: 4px; font-size: var(--text-lg);">
|
|
||||||
Summary Analysis
|
|
||||||
</div>
|
|
||||||
<div style="font-size: var(--text-sm); color: var(--accent-color);">
|
|
||||||
Quick overview and key insights
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="radio-option" data-value="detailed">
|
|
||||||
<input type="radio" name="analysisType" value="detailed" />
|
|
||||||
<div style="font-size: 20px;">📈</div>
|
|
||||||
<div>
|
|
||||||
<div style="font-weight: 600; margin-bottom: 4px; font-size: var(--text-lg);">
|
|
||||||
Detailed Analysis
|
|
||||||
</div>
|
|
||||||
<div style="font-size: var(--text-sm); color: var(--accent-color);">
|
|
||||||
Comprehensive statistical analysis
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="radio-option" data-value="statistical">
|
|
||||||
<input type="radio" name="analysisType" value="statistical" />
|
|
||||||
<div style="font-size: 20px;">📊</div>
|
|
||||||
<div>
|
|
||||||
<div style="font-weight: 600; margin-bottom: 4px; font-size: var(--text-lg);">
|
|
||||||
Statistical Analysis
|
|
||||||
</div>
|
|
||||||
<div style="font-size: var(--text-sm); color: var(--accent-color);">
|
|
||||||
Advanced statistics and trends
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<!-- Wallet Sidebar -->
|
|
||||||
<div class="wallet-section">
|
|
||||||
<div class="card">
|
|
||||||
<h3 class="section-title">💳 Your Wallet</h3>
|
|
||||||
|
|
||||||
<div class="wallet-balance" data-wallet-balance>
|
|
||||||
{% if user.is_authenticated %}
|
|
||||||
{{ user.wallet_balance|floatformat:2 }} AED
|
|
||||||
{% else %}
|
|
||||||
0.00 AED
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
<div class="balance-label">Available Balance</div>
|
|
||||||
|
|
||||||
{% if user.is_authenticated %}
|
|
||||||
{% if user.wallet_balance >= 5.00 %}
|
|
||||||
<button type="submit" form="dataAnalyzerForm" class="btn btn-primary process-btn" id="processButton">
|
|
||||||
📊 Analyze Data (5.00 AED)
|
|
||||||
</button>
|
|
||||||
{% else %}
|
|
||||||
<div class="insufficient-balance">
|
|
||||||
Insufficient balance! You need 5.00 AED.
|
|
||||||
</div>
|
|
||||||
<a href="{% url 'core:wallet' %}" class="btn btn-primary process-btn" style="text-decoration: none;">
|
|
||||||
💰 Top Up Wallet
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<a href="{% url 'authentication:login' %}" class="btn btn-primary process-btn" style="text-decoration: none;">
|
|
||||||
🔑 Login to Continue
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="usage-info">
|
|
||||||
<h4>💡 How it works</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Upload your data file (PDF, CSV, Excel)</li>
|
|
||||||
<li>Choose analysis type</li>
|
|
||||||
<li>Get comprehensive insights</li>
|
|
||||||
<li>Download detailed report</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Processing Status -->
|
|
||||||
<div id="processingStatus" class="processing-status">
|
|
||||||
<div class="status-icon">📊</div>
|
|
||||||
<div style="font-weight: 600; color: var(--primary-color);">Analyzing Your Data...</div>
|
|
||||||
<div style="font-size: 14px; color: var(--text-secondary); margin-top: 8px;" id="statusText">Processing data file...</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Results -->
|
|
||||||
<div id="analysisResults" class="results-card">
|
|
||||||
<div class="results-header">
|
|
||||||
<div style="font-size: 24px;">✅</div>
|
|
||||||
<h3 style="font-size: 20px; font-weight: 600; color: var(--text-primary); margin: 0;">Data Analysis Complete</h3>
|
|
||||||
<div style="background: var(--primary-color); color: white; padding: 6px 12px; border-radius: 6px; font-size: 14px; font-weight: 600; margin-left: auto;">✅ Complete</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="results-content" id="analysisContent">
|
|
||||||
<!-- Analysis results will be displayed here -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="action-buttons">
|
|
||||||
<button onclick="copyAnalysisReport()" class="btn btn-primary">📋 Copy Analysis</button>
|
|
||||||
<button onclick="downloadAnalysisReport()" class="btn btn-secondary">💾 Download Report</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_js %}
|
|
||||||
<script>
|
|
||||||
let selectedFile = null;
|
|
||||||
|
|
||||||
// File upload handling
|
|
||||||
const fileInput = document.getElementById('fileInput');
|
|
||||||
const fileUploadZone = document.getElementById('fileUploadZone');
|
|
||||||
const filePreview = document.getElementById('filePreview');
|
|
||||||
|
|
||||||
// Handle file input change
|
|
||||||
fileInput.addEventListener('change', function(e) {
|
|
||||||
if (e.target.files.length > 0) {
|
|
||||||
selectedFile = e.target.files[0];
|
|
||||||
showFilePreview(selectedFile);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Drag and drop functionality
|
|
||||||
fileUploadZone.addEventListener('dragover', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
fileUploadZone.classList.add('dragover');
|
|
||||||
});
|
|
||||||
|
|
||||||
fileUploadZone.addEventListener('dragleave', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
fileUploadZone.classList.remove('dragover');
|
|
||||||
});
|
|
||||||
|
|
||||||
fileUploadZone.addEventListener('drop', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
fileUploadZone.classList.remove('dragover');
|
|
||||||
|
|
||||||
const files = e.dataTransfer.files;
|
|
||||||
if (files.length > 0) {
|
|
||||||
const file = files[0];
|
|
||||||
if (isValidFile(file)) {
|
|
||||||
selectedFile = file;
|
|
||||||
fileInput.files = files;
|
|
||||||
showFilePreview(file);
|
|
||||||
} else {
|
|
||||||
AgentUtils.showToast('Please select a valid data file (PDF, CSV, Excel)', 'error');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show file preview
|
|
||||||
function showFilePreview(file) {
|
|
||||||
document.getElementById('fileName').textContent = file.name;
|
|
||||||
document.getElementById('fileSize').textContent = formatFileSize(file.size);
|
|
||||||
filePreview.style.display = 'flex';
|
|
||||||
fileUploadZone.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove file
|
|
||||||
function removeFile() {
|
|
||||||
selectedFile = null;
|
|
||||||
fileInput.value = '';
|
|
||||||
filePreview.style.display = 'none';
|
|
||||||
fileUploadZone.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate file type
|
|
||||||
function isValidFile(file) {
|
|
||||||
const validTypes = [
|
|
||||||
'application/pdf',
|
|
||||||
'text/csv',
|
|
||||||
'application/vnd.ms-excel',
|
|
||||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
||||||
];
|
|
||||||
return validTypes.includes(file.type) || file.name.match(/\.(pdf|csv|xlsx|xls)$/i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format file size
|
|
||||||
function formatFileSize(bytes) {
|
|
||||||
if (bytes === 0) return '0 Bytes';
|
|
||||||
const k = 1024;
|
|
||||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Radio button handling
|
|
||||||
document.querySelectorAll('.radio-option').forEach(option => {
|
|
||||||
option.addEventListener('click', function() {
|
|
||||||
document.querySelectorAll('.radio-option').forEach(opt => opt.classList.remove('selected'));
|
|
||||||
this.classList.add('selected');
|
|
||||||
this.querySelector('input[type="radio"]').checked = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Handle form submission
|
|
||||||
document.getElementById('dataAnalyzerForm').addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!selectedFile) {
|
|
||||||
AgentUtils.showToast('Please select a data file', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check user authentication
|
|
||||||
{% if not user.is_authenticated %}
|
|
||||||
window.location.href = "{% url 'authentication:login' %}";
|
|
||||||
return;
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
// Check wallet balance
|
|
||||||
const balance = {{ user.wallet_balance|default:0 }};
|
|
||||||
if (balance < 5.00) {
|
|
||||||
AgentUtils.showToast('Insufficient balance! You need 5.00 AED.', 'error');
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.href = "{% url 'core:wallet' %}";
|
|
||||||
}, 2000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show processing status
|
|
||||||
document.getElementById('processingStatus').style.display = 'block';
|
|
||||||
document.getElementById('processButton').disabled = true;
|
|
||||||
document.getElementById('processButton').innerHTML = '⏳ Processing...';
|
|
||||||
document.getElementById('analysisResults').style.display = 'none';
|
|
||||||
|
|
||||||
// Processing steps for user feedback
|
|
||||||
const steps = [
|
|
||||||
'Reading file structure...',
|
|
||||||
'Extracting data patterns...',
|
|
||||||
'Performing statistical analysis...',
|
|
||||||
'Generating insights...',
|
|
||||||
'Finalizing report...'
|
|
||||||
];
|
|
||||||
|
|
||||||
let currentStep = 0;
|
|
||||||
const stepInterval = setInterval(() => {
|
|
||||||
if (currentStep < steps.length) {
|
|
||||||
document.getElementById('statusText').textContent = steps[currentStep];
|
|
||||||
currentStep++;
|
|
||||||
} else {
|
|
||||||
clearInterval(stepInterval);
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
// Submit form data to backend
|
|
||||||
const formData = new FormData(this);
|
|
||||||
if (selectedFile) {
|
|
||||||
formData.append('file', selectedFile);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch(window.location.href, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
headers: {
|
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(result => {
|
|
||||||
clearInterval(stepInterval);
|
|
||||||
if (result.success && result.request_id) {
|
|
||||||
// Start polling for results
|
|
||||||
pollForResults(result.request_id);
|
|
||||||
} else {
|
|
||||||
// Handle immediate response
|
|
||||||
document.getElementById('processingStatus').style.display = 'none';
|
|
||||||
document.getElementById('processButton').disabled = false;
|
|
||||||
document.getElementById('processButton').innerHTML = '📊 Analyze Data (5.00 AED)';
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
AgentUtils.showToast(`❌ ${result.error}`, 'error');
|
|
||||||
} else {
|
|
||||||
displayResults(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
clearInterval(stepInterval);
|
|
||||||
console.error('Error:', error);
|
|
||||||
document.getElementById('processingStatus').style.display = 'none';
|
|
||||||
document.getElementById('processButton').disabled = false;
|
|
||||||
document.getElementById('processButton').innerHTML = '📊 Analyze Data (5.00 AED)';
|
|
||||||
AgentUtils.showToast('❌ Network error - please try again', 'error');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Display analysis results with custom Data Analyzer formatting
|
|
||||||
function displayResults(result) {
|
|
||||||
const resultsContainer = document.getElementById('analysisResults');
|
|
||||||
const contentElement = document.getElementById('analysisContent');
|
|
||||||
|
|
||||||
if (!resultsContainer || !contentElement) {
|
|
||||||
console.error('Results elements not found');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update wallet balance if provided
|
|
||||||
if (result.wallet_balance !== undefined) {
|
|
||||||
AgentUtils.updateWalletBalance(result.wallet_balance);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle errors
|
|
||||||
if (result.error) {
|
|
||||||
AgentUtils.showToast(`❌ ${result.error}`, 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract content with priority: report_text > insights_summary > raw_response.analysis
|
|
||||||
let content = '';
|
|
||||||
|
|
||||||
if (result.report_text && typeof result.report_text === 'string') {
|
|
||||||
content = result.report_text;
|
|
||||||
} else if (result.insights_summary && typeof result.insights_summary === 'string') {
|
|
||||||
content = result.insights_summary;
|
|
||||||
} else if (result.raw_response && result.raw_response.analysis && typeof result.raw_response.analysis === 'string') {
|
|
||||||
content = result.raw_response.analysis;
|
|
||||||
} else {
|
|
||||||
content = 'Data analysis completed successfully!';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse markdown content to HTML
|
|
||||||
const formattedContent = parseMarkdownToHTML(content);
|
|
||||||
|
|
||||||
// Update content
|
|
||||||
contentElement.innerHTML = formattedContent;
|
|
||||||
|
|
||||||
// Show results
|
|
||||||
resultsContainer.style.display = 'block';
|
|
||||||
resultsContainer.scrollIntoView({ behavior: 'smooth' });
|
|
||||||
|
|
||||||
// Show success message
|
|
||||||
const successMessage = result.success ? '✅ Data analysis completed and payment processed!' : '✅ Data analysis completed!';
|
|
||||||
AgentUtils.showToast(successMessage, 'success');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enhanced markdown parser for Data Analyzer results
|
|
||||||
function parseMarkdownToHTML(markdown) {
|
|
||||||
if (!markdown || typeof markdown !== 'string') {
|
|
||||||
return '<p>No content available.</p>';
|
|
||||||
}
|
|
||||||
|
|
||||||
let html = markdown;
|
|
||||||
|
|
||||||
// Convert headers (### Header, ## Header, # Header)
|
|
||||||
html = html.replace(/^### (.*$)/gm, '<h3 style="font-size: 18px; font-weight: 600; color: var(--text-primary); margin: 20px 0 12px 0; border-bottom: 2px solid var(--success-green); padding-bottom: 8px;">$1</h3>');
|
|
||||||
html = html.replace(/^## (.*$)/gm, '<h2 style="font-size: 20px; font-weight: 600; color: var(--text-primary); margin: 24px 0 16px 0; border-bottom: 2px solid var(--success-green); padding-bottom: 8px;">$1</h2>');
|
|
||||||
html = html.replace(/^# (.*$)/gm, '<h1 style="font-size: 22px; font-weight: 600; color: var(--text-primary); margin: 28px 0 18px 0; border-bottom: 2px solid var(--success-green); padding-bottom: 10px;">$1</h1>');
|
|
||||||
|
|
||||||
// Convert bold text (**text** or __text__)
|
|
||||||
html = html.replace(/\*\*(.*?)\*\*/g, '<strong style="font-weight: 600; color: var(--text-primary);">$1</strong>');
|
|
||||||
html = html.replace(/__(.*?)__/g, '<strong style="font-weight: 600; color: var(--text-primary);">$1</strong>');
|
|
||||||
|
|
||||||
// Convert italic text (*text* or _text_)
|
|
||||||
html = html.replace(/\*(.*?)\*/g, '<em style="font-style: italic; color: var(--text-secondary);">$1</em>');
|
|
||||||
html = html.replace(/_(.*?)_/g, '<em style="font-style: italic; color: var(--text-secondary);">$1</em>');
|
|
||||||
|
|
||||||
// Convert bullet points (- item or * item)
|
|
||||||
html = html.replace(/^[\s]*[-\*]\s+(.*)$/gm, '<li style="margin: 8px 0; padding-left: 8px; color: var(--text-primary);">$1</li>');
|
|
||||||
|
|
||||||
// Wrap consecutive list items in ul tags
|
|
||||||
html = html.replace(/(<li[^>]*>.*?<\/li>\s*)+/gs, function(match) {
|
|
||||||
return `<ul style="margin: 16px 0; padding-left: 20px; list-style-type: disc; color: var(--success-green);">${match}</ul>`;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Convert numbered lists (1. item, 2. item)
|
|
||||||
html = html.replace(/^\s*\d+\.\s+(.*)$/gm, '<li style="margin: 8px 0; padding-left: 8px; color: var(--text-primary);">$1</li>');
|
|
||||||
|
|
||||||
// Wrap consecutive numbered list items in ol tags
|
|
||||||
html = html.replace(/(<li[^>]*>.*?<\/li>\s*)+/gs, function(match) {
|
|
||||||
if (match.includes('ul style')) return match; // Skip if already wrapped in ul
|
|
||||||
return `<ol style="margin: 16px 0; padding-left: 20px; list-style-type: decimal; color: var(--success-green);">${match}</ol>`;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Convert line breaks to paragraphs
|
|
||||||
const paragraphs = html.split(/\n\s*\n/);
|
|
||||||
html = paragraphs.map(p => {
|
|
||||||
const trimmed = p.trim();
|
|
||||||
if (trimmed === '') return '';
|
|
||||||
|
|
||||||
// Skip if already wrapped in HTML tags
|
|
||||||
if (trimmed.startsWith('<h') || trimmed.startsWith('<ul') || trimmed.startsWith('<ol') || trimmed.startsWith('<li')) {
|
|
||||||
return trimmed;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `<p style="margin: 12px 0; line-height: 1.6; color: var(--text-primary);">${trimmed}</p>`;
|
|
||||||
}).filter(p => p !== '').join('');
|
|
||||||
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track polling and results to prevent duplicates
|
|
||||||
let resultsDisplayed = false;
|
|
||||||
let currentPollInterval = null;
|
|
||||||
|
|
||||||
// Poll for results
|
|
||||||
function pollForResults(requestId) {
|
|
||||||
let pollCount = 0;
|
|
||||||
const maxPolls = 60; // 60 seconds maximum for data analysis
|
|
||||||
resultsDisplayed = false; // Reset flag
|
|
||||||
|
|
||||||
// Clear any existing polling
|
|
||||||
if (currentPollInterval) {
|
|
||||||
clearInterval(currentPollInterval);
|
|
||||||
currentPollInterval = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPollInterval = setInterval(() => {
|
|
||||||
pollCount++;
|
|
||||||
|
|
||||||
fetch(`/agents/data-analyzer/status/${requestId}/`)
|
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP ${response.status}`);
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(result => {
|
|
||||||
if (result.status === 'completed' || result.status === 'failed') {
|
|
||||||
// Stop polling immediately
|
|
||||||
clearInterval(currentPollInterval);
|
|
||||||
currentPollInterval = null;
|
|
||||||
|
|
||||||
// Reset UI
|
|
||||||
document.getElementById('processingStatus').style.display = 'none';
|
|
||||||
document.getElementById('processButton').disabled = false;
|
|
||||||
document.getElementById('processButton').innerHTML = '📊 Analyze Data (5.00 AED)';
|
|
||||||
|
|
||||||
// Display results only once
|
|
||||||
if (!resultsDisplayed) {
|
|
||||||
resultsDisplayed = true;
|
|
||||||
displayResults(result);
|
|
||||||
}
|
|
||||||
} else if (pollCount >= maxPolls) {
|
|
||||||
clearInterval(currentPollInterval);
|
|
||||||
currentPollInterval = null;
|
|
||||||
document.getElementById('processingStatus').style.display = 'none';
|
|
||||||
document.getElementById('processButton').disabled = false;
|
|
||||||
document.getElementById('processButton').innerHTML = '📊 Analyze Data (5.00 AED)';
|
|
||||||
AgentUtils.showToast('❌ Processing timeout - please try again', 'error');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error polling results:', error);
|
|
||||||
clearInterval(currentPollInterval);
|
|
||||||
currentPollInterval = null;
|
|
||||||
document.getElementById('processingStatus').style.display = 'none';
|
|
||||||
document.getElementById('processButton').disabled = false;
|
|
||||||
document.getElementById('processButton').innerHTML = '📊 Analyze Data (5.00 AED)';
|
|
||||||
AgentUtils.showToast('❌ Network error during processing - please try again', 'error');
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function copyAnalysisReport() {
|
|
||||||
const reportText = AgentUtils.generateTextForExport('analysisContent');
|
|
||||||
AgentUtils.copyToClipboard(reportText, 'Analysis report copied to clipboard!');
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadAnalysisReport() {
|
|
||||||
const reportText = AgentUtils.generateTextForExport('analysisContent');
|
|
||||||
AgentUtils.downloadAsFile(reportText, `data-analysis-report-${Date.now()}.txt`, 'Analysis report downloaded!');
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,413 +0,0 @@
|
|||||||
{% extends 'base.html' %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block title %}Data Analyzer - NetCop AI Hub{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_css %}
|
|
||||||
<style>
|
|
||||||
/* Simple, beginner-friendly styling */
|
|
||||||
.simple-container {
|
|
||||||
max-width: 900px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-card {
|
|
||||||
background: white;
|
|
||||||
border: 1px solid #e5e7eb;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 24px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-form-group {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #374151;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-file-input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px;
|
|
||||||
border: 2px dashed #d1d5db;
|
|
||||||
border-radius: 6px;
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
|
||||||
background: #f9fafb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-file-input:hover {
|
|
||||||
border-color: #3b82f6;
|
|
||||||
background: #f0f9ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-radio-group {
|
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-radio-option {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 12px 16px;
|
|
||||||
border: 1px solid #d1d5db;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-radio-option:hover {
|
|
||||||
border-color: #3b82f6;
|
|
||||||
background: #f0f9ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-radio-option input[type="radio"]:checked + label {
|
|
||||||
border-color: #3b82f6;
|
|
||||||
background: #f0f9ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-btn {
|
|
||||||
padding: 12px 24px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 6px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-btn-primary {
|
|
||||||
background: #3b82f6;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-btn-primary:hover {
|
|
||||||
background: #2563eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-btn-secondary {
|
|
||||||
background: #6b7280;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-wallet-info {
|
|
||||||
background: #f3f4f6;
|
|
||||||
padding: 16px;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-loading {
|
|
||||||
text-align: center;
|
|
||||||
padding: 20px;
|
|
||||||
background: #fef3c7;
|
|
||||||
border-radius: 6px;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-results {
|
|
||||||
background: #f0f9ff;
|
|
||||||
border: 1px solid #3b82f6;
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 20px;
|
|
||||||
margin-top: 20px;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-error {
|
|
||||||
background: #fef2f2;
|
|
||||||
border: 1px solid #ef4444;
|
|
||||||
color: #dc2626;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-success {
|
|
||||||
background: #f0fdf4;
|
|
||||||
border: 1px solid #22c55e;
|
|
||||||
color: #16a34a;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 300px;
|
|
||||||
gap: 20px;
|
|
||||||
align-items: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.simple-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="simple-container">
|
|
||||||
<div class="simple-grid">
|
|
||||||
<!-- Main Content -->
|
|
||||||
<div>
|
|
||||||
<div class="simple-card">
|
|
||||||
<h2>📊 Data Analyzer</h2>
|
|
||||||
<p>Upload your data file and get AI-powered analysis</p>
|
|
||||||
|
|
||||||
<form id="simpleForm" method="POST" enctype="multipart/form-data">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<!-- File Upload -->
|
|
||||||
<div class="simple-form-group">
|
|
||||||
<label class="simple-label">📁 Upload Data File</label>
|
|
||||||
<input type="file" id="dataFile" name="file" accept=".pdf,.csv,.xlsx,.xls"
|
|
||||||
class="simple-file-input" required>
|
|
||||||
<small>Supports: PDF, CSV, Excel files</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Analysis Type -->
|
|
||||||
<div class="simple-form-group">
|
|
||||||
<label class="simple-label">🔍 Analysis Type</label>
|
|
||||||
<div class="simple-radio-group">
|
|
||||||
<div class="simple-radio-option">
|
|
||||||
<input type="radio" id="summary" name="analysisType" value="summary" checked>
|
|
||||||
<label for="summary">📋 Summary</label>
|
|
||||||
</div>
|
|
||||||
<div class="simple-radio-option">
|
|
||||||
<input type="radio" id="detailed" name="analysisType" value="detailed">
|
|
||||||
<label for="detailed">📈 Detailed</label>
|
|
||||||
</div>
|
|
||||||
<div class="simple-radio-option">
|
|
||||||
<input type="radio" id="statistical" name="analysisType" value="statistical">
|
|
||||||
<label for="statistical">📊 Statistical</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Loading -->
|
|
||||||
<div id="loadingDiv" class="simple-loading">
|
|
||||||
<div>⏳ Analyzing your data...</div>
|
|
||||||
<div id="loadingText">Processing file...</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Results -->
|
|
||||||
<div id="resultsDiv" class="simple-results">
|
|
||||||
<h3>✅ Analysis Complete</h3>
|
|
||||||
<div id="resultsContent"></div>
|
|
||||||
<div style="margin-top: 16px;">
|
|
||||||
<button onclick="copyResults()" class="simple-btn simple-btn-secondary">📋 Copy</button>
|
|
||||||
<button onclick="downloadResults()" class="simple-btn simple-btn-secondary">💾 Download</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Wallet Sidebar -->
|
|
||||||
<div>
|
|
||||||
<div class="simple-card">
|
|
||||||
<h3>💳 Your Wallet</h3>
|
|
||||||
<div class="simple-wallet-info">
|
|
||||||
<div style="font-size: 24px; font-weight: bold;">
|
|
||||||
<span id="walletBalance">{{ user.wallet_balance|floatformat:2 }}</span> AED
|
|
||||||
</div>
|
|
||||||
<div style="color: #6b7280;">Available Balance</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if user.is_authenticated %}
|
|
||||||
{% if user.wallet_balance >= 5.00 %}
|
|
||||||
<button type="submit" form="simpleForm" class="simple-btn simple-btn-primary"
|
|
||||||
id="analyzeBtn" style="width: 100%;">
|
|
||||||
📊 Analyze Data (5.00 AED)
|
|
||||||
</button>
|
|
||||||
{% else %}
|
|
||||||
<div class="simple-error">
|
|
||||||
Insufficient balance! You need 5.00 AED.
|
|
||||||
</div>
|
|
||||||
<a href="{% url 'core:wallet' %}" class="simple-btn simple-btn-primary"
|
|
||||||
style="width: 100%; text-decoration: none;">
|
|
||||||
💰 Top Up Wallet
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<a href="{% url 'authentication:login' %}" class="simple-btn simple-btn-primary"
|
|
||||||
style="width: 100%; text-decoration: none;">
|
|
||||||
🔑 Login to Continue
|
|
||||||
</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="simple-card">
|
|
||||||
<h4>💡 How it works</h4>
|
|
||||||
<ol>
|
|
||||||
<li>Upload your data file</li>
|
|
||||||
<li>Choose analysis type</li>
|
|
||||||
<li>Get AI-powered insights</li>
|
|
||||||
<li>Copy or download results</li>
|
|
||||||
</ol>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Simple JavaScript - beginner friendly
|
|
||||||
let currentResults = '';
|
|
||||||
|
|
||||||
// Form submission
|
|
||||||
document.getElementById('simpleForm').addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
// Check if file is selected
|
|
||||||
const fileInput = document.getElementById('dataFile');
|
|
||||||
if (!fileInput.files || fileInput.files.length === 0) {
|
|
||||||
alert('Please select a data file');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check authentication
|
|
||||||
{% if not user.is_authenticated %}
|
|
||||||
window.location.href = "{% url 'authentication:login' %}";
|
|
||||||
return;
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
// Check wallet balance
|
|
||||||
const balance = {{ user.wallet_balance|default:0 }};
|
|
||||||
if (balance < 5.00) {
|
|
||||||
alert('Insufficient balance! You need 5.00 AED.');
|
|
||||||
window.location.href = "{% url 'core:wallet' %}";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show loading
|
|
||||||
document.getElementById('loadingDiv').style.display = 'block';
|
|
||||||
document.getElementById('resultsDiv').style.display = 'none';
|
|
||||||
document.getElementById('analyzeBtn').disabled = true;
|
|
||||||
document.getElementById('analyzeBtn').textContent = '⏳ Processing...';
|
|
||||||
|
|
||||||
// Submit form
|
|
||||||
const formData = new FormData(this);
|
|
||||||
|
|
||||||
fetch(window.location.href, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
headers: {
|
|
||||||
'X-Requested-With': 'XMLHttpRequest'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(result => {
|
|
||||||
if (result.success && result.request_id) {
|
|
||||||
// Start checking for results
|
|
||||||
checkResults(result.request_id);
|
|
||||||
} else {
|
|
||||||
showError(result.error || 'Processing failed');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error:', error);
|
|
||||||
showError('Network error - please try again');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check results (simplified polling)
|
|
||||||
function checkResults(requestId) {
|
|
||||||
fetch(`/agents/data-analyzer/status/${requestId}/`)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(result => {
|
|
||||||
if (result.status === 'completed') {
|
|
||||||
hideLoading();
|
|
||||||
if (result.success) {
|
|
||||||
showResults(result);
|
|
||||||
updateWalletBalance(result.wallet_balance);
|
|
||||||
} else {
|
|
||||||
showError('Analysis failed');
|
|
||||||
}
|
|
||||||
} else if (result.status === 'failed') {
|
|
||||||
hideLoading();
|
|
||||||
showError('Analysis failed');
|
|
||||||
} else {
|
|
||||||
// Still processing, check again in 2 seconds
|
|
||||||
setTimeout(() => checkResults(requestId), 2000);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error checking results:', error);
|
|
||||||
hideLoading();
|
|
||||||
showError('Error checking results');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show results
|
|
||||||
function showResults(result) {
|
|
||||||
// Get content from different possible fields
|
|
||||||
let content = result.report_text || result.insights_summary ||
|
|
||||||
(result.raw_response && result.raw_response.analysis) ||
|
|
||||||
'Analysis completed successfully!';
|
|
||||||
|
|
||||||
// Simple text formatting (no complex markdown)
|
|
||||||
content = content.replace(/\*\*/g, '').replace(/\n/g, '<br>');
|
|
||||||
|
|
||||||
currentResults = content;
|
|
||||||
document.getElementById('resultsContent').innerHTML = content;
|
|
||||||
document.getElementById('resultsDiv').style.display = 'block';
|
|
||||||
|
|
||||||
// Show success message
|
|
||||||
showMessage('✅ Analysis completed and payment processed!', 'success');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper functions
|
|
||||||
function hideLoading() {
|
|
||||||
document.getElementById('loadingDiv').style.display = 'none';
|
|
||||||
document.getElementById('analyzeBtn').disabled = false;
|
|
||||||
document.getElementById('analyzeBtn').textContent = '📊 Analyze Data (5.00 AED)';
|
|
||||||
}
|
|
||||||
|
|
||||||
function showError(message) {
|
|
||||||
hideLoading();
|
|
||||||
showMessage('❌ ' + message, 'error');
|
|
||||||
}
|
|
||||||
|
|
||||||
function showMessage(message, type) {
|
|
||||||
// Simple alert for now (can be improved later)
|
|
||||||
alert(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateWalletBalance(newBalance) {
|
|
||||||
if (newBalance !== undefined) {
|
|
||||||
document.getElementById('walletBalance').textContent = newBalance.toFixed(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function copyResults() {
|
|
||||||
if (currentResults) {
|
|
||||||
navigator.clipboard.writeText(currentResults.replace(/<br>/g, '\n'))
|
|
||||||
.then(() => alert('📋 Results copied to clipboard!'))
|
|
||||||
.catch(() => alert('Failed to copy results'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadResults() {
|
|
||||||
if (currentResults) {
|
|
||||||
const blob = new Blob([currentResults.replace(/<br>/g, '\n')], { type: 'text/plain' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = `data-analysis-${Date.now()}.txt`;
|
|
||||||
a.click();
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
alert('💾 Results downloaded!');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,393 +0,0 @@
|
|||||||
# Agent Setup Checklist - Error-Free Creation Guide
|
|
||||||
## Steps to Complete After Running `create_agent` Command
|
|
||||||
|
|
||||||
This checklist covers the **6 essential steps** needed after running the automated `create_agent` command to make your agent fully functional. **Updated with debugging insights from the successful 5 Whys Agent implementation.**
|
|
||||||
|
|
||||||
**✅ The automated system now generates all code files including models, views, processors, and admin interface!**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 **Success Patterns from 5 Whys Agent**
|
|
||||||
|
|
||||||
The 5 Whys Analyzer represents the most robust agent implementation with these key features:
|
|
||||||
- **Dual-mode processing**: Free chat interactions + paid report generation
|
|
||||||
- **Session-based architecture**: UUID tracking with persistent chat history
|
|
||||||
- **Delayed wallet deduction**: Only charge after successful processing
|
|
||||||
- **Comprehensive error handling**: Graceful failure recovery
|
|
||||||
- **Smart status tracking**: Proper request lifecycle management
|
|
||||||
|
|
||||||
**Apply these patterns to achieve error-free agent creation.**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Example Command
|
|
||||||
```bash
|
|
||||||
python manage.py create_agent "PDF Analyzer" "pdf-analyzer" api \
|
|
||||||
--category utilities --price 5.0 \
|
|
||||||
--api-base-url "https://api.docparser.com/v1/process" \
|
|
||||||
--api-key-env "DOCPARSER_API_KEY" --auth-method bearer
|
|
||||||
```
|
|
||||||
|
|
||||||
After running this command, follow these steps:
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ **Step 1: Add to Django Settings**
|
|
||||||
|
|
||||||
**File:** `netcop_hub/settings.py`
|
|
||||||
|
|
||||||
**Add your new agent to INSTALLED_APPS:**
|
|
||||||
```python
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
|
|
||||||
# Core apps
|
|
||||||
'core',
|
|
||||||
'authentication',
|
|
||||||
'wallet',
|
|
||||||
'agent_base',
|
|
||||||
|
|
||||||
# Agent apps
|
|
||||||
'weather_reporter',
|
|
||||||
'agent_pdf_analyzer', # ← ADD THIS LINE
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ **Step 2: Register URL Routing**
|
|
||||||
|
|
||||||
**File:** `netcop_hub/urls.py`
|
|
||||||
|
|
||||||
**Add URL pattern for your agent:**
|
|
||||||
```python
|
|
||||||
urlpatterns = [
|
|
||||||
path('admin/', admin.site.urls),
|
|
||||||
path('auth/', include('authentication.urls')),
|
|
||||||
path('agents/weather-reporter/', include('weather_reporter.urls')),
|
|
||||||
path('agents/pdf-analyzer/', include('agent_pdf_analyzer.urls')), # ← ADD THIS LINE
|
|
||||||
path('', include('core.urls')),
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
**⚠️ Important:** Add agent URLs **before** the core URLs (the line with `path('', include('core.urls'))`).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ **Step 3: Run Database Migrations**
|
|
||||||
|
|
||||||
**Terminal Commands:**
|
|
||||||
```bash
|
|
||||||
# Create migrations for your new agent
|
|
||||||
python manage.py makemigrations agent_pdf_analyzer
|
|
||||||
|
|
||||||
# Apply migrations to database
|
|
||||||
python manage.py migrate
|
|
||||||
```
|
|
||||||
|
|
||||||
**Expected Output:**
|
|
||||||
```
|
|
||||||
Migrations for 'agent_pdf_analyzer':
|
|
||||||
agent_pdf_analyzer/migrations/0001_initial.py
|
|
||||||
- Create model PdfAnalyzerRequest
|
|
||||||
- Create model PdfAnalyzerResponse
|
|
||||||
|
|
||||||
Operations to perform:
|
|
||||||
Apply all migrations: ...
|
|
||||||
Running migrations:
|
|
||||||
Applying agent_pdf_analyzer.0001_initial... OK
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ **Step 4: Create Marketplace Entry**
|
|
||||||
|
|
||||||
**Method A: Django Shell (Recommended)**
|
|
||||||
```bash
|
|
||||||
python manage.py shell
|
|
||||||
```
|
|
||||||
|
|
||||||
```python
|
|
||||||
from agent_base.models import BaseAgent
|
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
BaseAgent.objects.create(
|
|
||||||
name="PDF Analyzer",
|
|
||||||
slug="pdf-analyzer",
|
|
||||||
description="Extract text, generate summaries, and analyze sentiment from PDF documents",
|
|
||||||
category="utilities",
|
|
||||||
price=Decimal('5.00'),
|
|
||||||
icon="📄",
|
|
||||||
agent_type="api",
|
|
||||||
rating=Decimal('4.5'),
|
|
||||||
review_count=0,
|
|
||||||
is_active=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Verify it was created
|
|
||||||
print("Agent created:", BaseAgent.objects.filter(slug='pdf-analyzer').exists())
|
|
||||||
```
|
|
||||||
|
|
||||||
**Method B: Admin Interface**
|
|
||||||
1. Go to `http://localhost:8000/admin/`
|
|
||||||
2. Login with superuser account
|
|
||||||
3. Click "Base agents" under "AGENT_BASE"
|
|
||||||
4. Click "Add Base Agent"
|
|
||||||
5. Fill in the form with agent details
|
|
||||||
6. Save
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ **Step 5: Add Environment Variables**
|
|
||||||
|
|
||||||
**File:** `.env`
|
|
||||||
|
|
||||||
**Add API credentials for your agent:**
|
|
||||||
```bash
|
|
||||||
# Existing variables...
|
|
||||||
OPENWEATHER_API_KEY=your_openweather_api_key_here
|
|
||||||
|
|
||||||
# Add your new agent's API key
|
|
||||||
DOCPARSER_API_KEY=your_actual_api_key_here
|
|
||||||
```
|
|
||||||
|
|
||||||
**For webhook agents, add webhook URLs:**
|
|
||||||
```bash
|
|
||||||
# For webhook-based agents
|
|
||||||
N8N_WEBHOOK_PDF_ANALYZER=https://your-n8n-instance.com/webhook/pdf-analyzer
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ **Step 6: Create Agent Template**
|
|
||||||
|
|
||||||
**The automated system creates the code structure, but you need to create the template:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create the template directory and file:
|
|
||||||
mkdir -p [agent_name]/templates/
|
|
||||||
```
|
|
||||||
|
|
||||||
**Copy and customize from the weather reporter template:**
|
|
||||||
```bash
|
|
||||||
# Copy the weather reporter template as a starting point:
|
|
||||||
cp weather_reporter/templates/detail.html [agent_name]/templates/detail.html
|
|
||||||
|
|
||||||
# Then customize the template for your specific agent
|
|
||||||
```
|
|
||||||
|
|
||||||
**Template location should be:**
|
|
||||||
```bash
|
|
||||||
# Your agent templates should be in:
|
|
||||||
agent_[name]/templates/agent_[name]/detail.html
|
|
||||||
|
|
||||||
# Example for PDF Analyzer:
|
|
||||||
agent_pdf_analyzer/templates/agent_pdf_analyzer/detail.html
|
|
||||||
|
|
||||||
# Example for Data Analyzer:
|
|
||||||
data_analyzer/templates/data_analyzer/detail.html
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 **Step 7: Test Your Agent**
|
|
||||||
|
|
||||||
### **7.1 Check Django Configuration**
|
|
||||||
```bash
|
|
||||||
python manage.py check
|
|
||||||
```
|
|
||||||
**Expected:** `System check identified no issues (0 silenced).`
|
|
||||||
|
|
||||||
### **7.2 Test Template Loading**
|
|
||||||
```bash
|
|
||||||
python manage.py shell -c "
|
|
||||||
from django.template.loader import get_template
|
|
||||||
try:
|
|
||||||
template = get_template('detail.html')
|
|
||||||
print('✅ Template found successfully')
|
|
||||||
except Exception as e:
|
|
||||||
print('❌ Template error:', e)
|
|
||||||
"
|
|
||||||
```
|
|
||||||
**Expected:** `✅ Template found successfully`
|
|
||||||
|
|
||||||
### **7.3 Test URL Routing**
|
|
||||||
```bash
|
|
||||||
python manage.py shell -c "from django.urls import reverse; print('Agent URL:', reverse('core:agent_detail', args=['pdf-analyzer']))"
|
|
||||||
```
|
|
||||||
**Expected:** `Agent URL: /agents/pdf-analyzer/`
|
|
||||||
|
|
||||||
### **7.4 Test in Browser**
|
|
||||||
1. **Start server:** `python manage.py runserver`
|
|
||||||
2. **Visit marketplace:** `http://localhost:8000/marketplace/`
|
|
||||||
3. **Verify agent appears** in the list
|
|
||||||
4. **Click "Use Agent"** button
|
|
||||||
5. **Verify agent page loads** correctly (should redirect to login if not authenticated)
|
|
||||||
6. **Test authentication flow** (login → redirect back to agent page)
|
|
||||||
|
|
||||||
### **7.5 Test Complete Flow**
|
|
||||||
1. **Login** with test user
|
|
||||||
2. **Add wallet balance** (if needed)
|
|
||||||
3. **Submit agent form** with test data
|
|
||||||
4. **Verify request processes** successfully
|
|
||||||
5. **Check wallet deduction** occurred
|
|
||||||
6. **Verify results display** correctly
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🐛 **Common Issues & Quick Fixes** *(Learned from 5 Whys Debugging)*
|
|
||||||
|
|
||||||
### **Issue 1: "No module named 'agent_pdf_analyzer'"**
|
|
||||||
**Root Cause:** App not added to Django settings
|
|
||||||
**Fix:** Make sure you added the app to `INSTALLED_APPS` in settings.py
|
|
||||||
**Prevention:** Use the automated validation script (coming soon)
|
|
||||||
|
|
||||||
### **Issue 2: "TemplateDoesNotExist: detail.html"**
|
|
||||||
**Root Cause:** Template in wrong location or server cache
|
|
||||||
**Fix:** Ensure template is in correct location within the agent app:
|
|
||||||
```bash
|
|
||||||
# Template should be at:
|
|
||||||
agent_[name]/templates/agent_[name]/detail.html
|
|
||||||
|
|
||||||
# NOT just:
|
|
||||||
agent_[name]/templates/detail.html
|
|
||||||
|
|
||||||
# CRITICAL: Restart Django server after moving templates
|
|
||||||
```
|
|
||||||
**5 Whys Learning:** Template organization is crucial for reliability
|
|
||||||
|
|
||||||
### **Issue 3: "NoReverseMatch: Reverse for 'wallet' not found"**
|
|
||||||
**Root Cause:** Missing URL namespaces in templates
|
|
||||||
**Fix:** Check template URLs use proper namespaces:
|
|
||||||
```html
|
|
||||||
<!-- Wrong -->
|
|
||||||
{% url 'wallet' %}
|
|
||||||
|
|
||||||
<!-- Correct -->
|
|
||||||
{% url 'core:wallet' %}
|
|
||||||
```
|
|
||||||
**5 Whys Learning:** Always use namespaced URLs for reliability
|
|
||||||
|
|
||||||
### **Issue 4: "Agent not found" in marketplace**
|
|
||||||
**Root Cause:** BaseAgent entry missing or wrong slug
|
|
||||||
**Fix:** Verify BaseAgent was created with correct slug:
|
|
||||||
```bash
|
|
||||||
python manage.py shell -c "from agent_base.models import BaseAgent; print([a.slug for a in BaseAgent.objects.all()])"
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Issue 5: Agent page shows 404**
|
|
||||||
**Root Cause:** URL registration order is wrong
|
|
||||||
**Fix:** Check URL registration order in `netcop_hub/urls.py` - agent URLs must come before core URLs.
|
|
||||||
**5 Whys Learning:** URL order matters for Django routing
|
|
||||||
|
|
||||||
### **Issue 6: API key errors**
|
|
||||||
**Root Cause:** Environment variable name mismatch
|
|
||||||
**Fix:** Verify environment variable name matches processor:
|
|
||||||
```python
|
|
||||||
# In processor.py
|
|
||||||
api_key_env = 'DOCPARSER_API_KEY' # Must match .env file
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Issue 7: Wallet deduction errors (5 Whys Pattern)**
|
|
||||||
**Root Cause:** Deducting balance before processing success
|
|
||||||
**Fix:** Follow the 5 Whys pattern - only deduct after successful processing:
|
|
||||||
```python
|
|
||||||
# ❌ Wrong - deduct before processing
|
|
||||||
user.deduct_balance(cost, description, agent_slug)
|
|
||||||
response = process_request()
|
|
||||||
|
|
||||||
# ✅ Correct - deduct after success (5 Whys pattern)
|
|
||||||
response = process_request()
|
|
||||||
if response.success:
|
|
||||||
user.deduct_balance(cost, description, agent_slug)
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Issue 8: Migration conflicts**
|
|
||||||
**Root Cause:** Django migrations out of sync with database
|
|
||||||
**Fix:** Create empty migration to sync state:
|
|
||||||
```bash
|
|
||||||
# Create manual sync migration
|
|
||||||
python manage.py makemigrations [agent_name] --empty
|
|
||||||
# Edit migration to match your needs
|
|
||||||
python manage.py migrate
|
|
||||||
```
|
|
||||||
**5 Whys Learning:** Migration conflicts are common - be prepared to sync manually
|
|
||||||
|
|
||||||
### **Issue 9: Session management errors (Advanced Agents)**
|
|
||||||
**Root Cause:** No persistent session tracking
|
|
||||||
**Fix:** Implement session-based architecture like 5 Whys:
|
|
||||||
```python
|
|
||||||
# Add to your models
|
|
||||||
session_id = models.CharField(max_length=100, default=uuid.uuid4, db_index=True)
|
|
||||||
chat_messages = models.JSONField(default=list)
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Issue 10: Status tracking problems**
|
|
||||||
**Root Cause:** Inconsistent request status management
|
|
||||||
**Fix:** Use proper status lifecycle like 5 Whys:
|
|
||||||
```python
|
|
||||||
# Status flow: pending → processing → completed/failed
|
|
||||||
request_obj.status = 'processing'
|
|
||||||
request_obj.save()
|
|
||||||
# ... do processing ...
|
|
||||||
request_obj.status = 'completed' if success else 'failed'
|
|
||||||
request_obj.save()
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 **Quick Checklist Summary** *(Error-Free Process)*
|
|
||||||
|
|
||||||
After running `create_agent`, complete these **7 critical steps** (updated with 5 Whys learnings):
|
|
||||||
|
|
||||||
- [ ] **Settings:** Add agent to `INSTALLED_APPS` in `netcop_hub/settings.py`
|
|
||||||
- [ ] **URLs:** Add URL pattern to `netcop_hub/urls.py` **BEFORE core URLs**
|
|
||||||
- [ ] **Database:** Run `makemigrations` and `migrate` (watch for conflicts)
|
|
||||||
- [ ] **Marketplace:** Verify `BaseAgent` entry created correctly
|
|
||||||
- [ ] **Environment:** Add API keys/webhook URLs to `.env`
|
|
||||||
- [ ] **Template:** Create `agent_[name]/templates/agent_[name]/detail.html`
|
|
||||||
- [ ] **Validation:** Run complete test flow including wallet integration
|
|
||||||
|
|
||||||
**5 Whys Bonus Validations:**
|
|
||||||
- [ ] **Template Loading:** Restart Django server after template creation
|
|
||||||
- [ ] **URL Namespaces:** Use `{% url 'core:wallet' %}` not `{% url 'wallet' %}`
|
|
||||||
- [ ] **Error Handling:** Implement try-catch blocks in processor
|
|
||||||
- [ ] **Wallet Logic:** Only deduct balance after successful processing
|
|
||||||
- [ ] **Status Tracking:** Use pending → processing → completed/failed flow
|
|
||||||
|
|
||||||
**Total time:** ~15-20 minutes (includes validation steps)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 **You're Done!** *(Error-Free Agent)*
|
|
||||||
|
|
||||||
Your agent should now be:
|
|
||||||
✅ **Visible** in the marketplace
|
|
||||||
✅ **Accessible** via direct URL
|
|
||||||
✅ **Functional** with authentication
|
|
||||||
✅ **Processing** requests successfully
|
|
||||||
✅ **Integrated** with wallet system
|
|
||||||
✅ **Error-resistant** with proper handling
|
|
||||||
✅ **Session-aware** (if applicable)
|
|
||||||
✅ **Status-tracked** throughout lifecycle
|
|
||||||
|
|
||||||
**Success Validation** (5 Whys Standard):
|
|
||||||
- Agent processes test request without errors
|
|
||||||
- Wallet deduction only happens after successful processing
|
|
||||||
- Templates load correctly with namespaced URLs
|
|
||||||
- Error states are handled gracefully
|
|
||||||
- Status updates correctly throughout request lifecycle
|
|
||||||
|
|
||||||
**Next Steps:**
|
|
||||||
- Consider implementing dual-mode processing (free chat + paid reports)
|
|
||||||
- Add session management for complex interactions
|
|
||||||
- Enhance error handling with comprehensive try-catch blocks
|
|
||||||
- Monitor usage patterns and optimize based on 5 Whys learnings
|
|
||||||
- Document any new patterns for future agents
|
|
||||||
|
|
||||||
**🎯 Remember:** Follow the 5 Whys Agent patterns for maximum reliability!
|
|
||||||
933
docs/CLAUDE.md
933
docs/CLAUDE.md
@ -1,933 +0,0 @@
|
|||||||
# CLAUDE.md
|
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
|
|
||||||
NetCop Hub is a Django-based AI agent marketplace that allows users to purchase and use various AI-powered agents for tasks like social media ad generation, data analysis, weather reporting, and more. The system features a wallet-based payment system with Stripe integration using API-based payment verification for reliable, instant transactions.
|
|
||||||
|
|
||||||
### Project Structure
|
|
||||||
```
|
|
||||||
netcop_django/
|
|
||||||
├── 📁 docs/ # All documentation, guides, and logs
|
|
||||||
├── 📁 tests/ # All test files and scripts
|
|
||||||
├── 📁 agent_base/ # Agent framework and creation tools
|
|
||||||
├── 📁 authentication/ # User management system
|
|
||||||
├── 📁 core/ # Main app (homepage, marketplace, wallet)
|
|
||||||
├── 📁 wallet/ # Payment and transaction system
|
|
||||||
├── 📁 weather_reporter/ # Example individual agent app
|
|
||||||
│ └── templates/ # Agent-specific templates (namespaced)
|
|
||||||
│ └── weather_reporter/
|
|
||||||
│ └── detail.html
|
|
||||||
├── 📁 templates/ # Global templates (core, auth)
|
|
||||||
├── 📁 static/ # Static assets (CSS, JS, images)
|
|
||||||
├── 📁 media/ # User-uploaded files
|
|
||||||
├── 📁 netcop_hub/ # Django project configuration
|
|
||||||
└── manage.py # Django management commands
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Architecture Components
|
|
||||||
|
|
||||||
### Individual Agent Architecture
|
|
||||||
The project uses a modular individual agent architecture where each agent is a separate Django app:
|
|
||||||
|
|
||||||
- **Base Framework**: `agent_base/` provides common functionality:
|
|
||||||
- `BaseAgent` model for agent marketplace catalog
|
|
||||||
- `BaseAgentRequest`/`BaseAgentResponse` abstract models for tracking
|
|
||||||
- `BaseAgentProcessor` abstract class for webhook handling
|
|
||||||
- `BaseAgentView` abstract class for form processing and authentication
|
|
||||||
|
|
||||||
- **Individual Agent Apps**: Each agent has its own app (`agent_social_ads/`, `agent_weather/`, etc.):
|
|
||||||
- Custom models extending base classes
|
|
||||||
- Specialized processors for webhook communication
|
|
||||||
- Individual views and URL routing
|
|
||||||
- Separate templates and static files
|
|
||||||
|
|
||||||
### Webhook Processing System
|
|
||||||
All agents communicate with external AI services via N8N webhooks:
|
|
||||||
- Processors handle data preparation, request/response processing
|
|
||||||
- Webhook URLs configured via environment variables
|
|
||||||
- Built-in error handling and timeout management
|
|
||||||
- Processing time tracking and logging
|
|
||||||
|
|
||||||
### User Authentication & Wallet System
|
|
||||||
- Custom User model with wallet balance functionality
|
|
||||||
- Stripe integration for payments (`wallet/stripe_handler.py`)
|
|
||||||
- Transaction tracking via `WalletTransaction` model
|
|
||||||
- **IMPORTANT**: Wallet deduction happens ONLY after successful processing (not before)
|
|
||||||
- Real-time balance updates in frontend after successful agent execution
|
|
||||||
|
|
||||||
## Essential Commands
|
|
||||||
|
|
||||||
### Development Setup
|
|
||||||
```bash
|
|
||||||
# Create and activate virtual environment
|
|
||||||
python -m venv venv
|
|
||||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
||||||
|
|
||||||
# Install dependencies (no requirements.txt - manual installation needed)
|
|
||||||
pip install django djangorestframework python-decouple stripe requests
|
|
||||||
|
|
||||||
# Database setup
|
|
||||||
python manage.py makemigrations
|
|
||||||
python manage.py migrate
|
|
||||||
|
|
||||||
# Create superuser
|
|
||||||
python manage.py createsuperuser
|
|
||||||
|
|
||||||
# Populate agents catalog
|
|
||||||
python manage.py populate_base_agents
|
|
||||||
```
|
|
||||||
|
|
||||||
### Running the Application
|
|
||||||
```bash
|
|
||||||
# Start development server
|
|
||||||
python manage.py runserver
|
|
||||||
|
|
||||||
# Run with specific settings
|
|
||||||
python manage.py runserver --settings=netcop_hub.settings
|
|
||||||
```
|
|
||||||
|
|
||||||
### Database Management
|
|
||||||
```bash
|
|
||||||
# Create new migrations
|
|
||||||
python manage.py makemigrations [app_name]
|
|
||||||
|
|
||||||
# Apply migrations
|
|
||||||
python manage.py migrate
|
|
||||||
|
|
||||||
# Reset database (if needed)
|
|
||||||
python manage.py flush
|
|
||||||
|
|
||||||
# Django shell
|
|
||||||
python manage.py shell
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
```bash
|
|
||||||
# Run all tests
|
|
||||||
python manage.py test
|
|
||||||
|
|
||||||
# Run specific app tests
|
|
||||||
python manage.py test agent_social_ads
|
|
||||||
|
|
||||||
# Run with verbosity
|
|
||||||
python manage.py test --verbosity=2
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment Configuration
|
|
||||||
|
|
||||||
The project uses python-decouple for environment management. Key variables in `.env`:
|
|
||||||
|
|
||||||
### Required Settings
|
|
||||||
- `SECRET_KEY`: Django secret key
|
|
||||||
- `DEBUG`: Development mode flag
|
|
||||||
- `ALLOWED_HOSTS`: Comma-separated host list
|
|
||||||
- `DATABASE_URL`: PostgreSQL connection string (uses SQLite by default)
|
|
||||||
|
|
||||||
### Webhook Configuration
|
|
||||||
Each agent requires webhook URLs in format:
|
|
||||||
- `N8N_WEBHOOK_[AGENT_NAME]`: Django backend webhook URL
|
|
||||||
- `NEXT_PUBLIC_N8N_WEBHOOK_[AGENT_NAME]`: Frontend webhook URL
|
|
||||||
|
|
||||||
### Payment Integration
|
|
||||||
- `STRIPE_SECRET_KEY`: Stripe API secret key
|
|
||||||
- `STRIPE_WEBHOOK_SECRET`: Stripe webhook signing secret
|
|
||||||
- `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY`: Stripe publishable key
|
|
||||||
|
|
||||||
## Agent Creation System (Automated)
|
|
||||||
|
|
||||||
### Automated Agent Creation Command (✅ FULLY FUNCTIONAL)
|
|
||||||
The project features a sophisticated automated agent creation system via the `create_agent` management command with complete Django template generation:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create webhook-based agent (N8N integration)
|
|
||||||
python manage.py create_agent "Agent Name" "agent-slug" webhook \
|
|
||||||
--category utilities --price 2.5 \
|
|
||||||
--webhook-url "https://webhook.url" --agent-id "123"
|
|
||||||
|
|
||||||
# Create API-based agent (Direct API integration)
|
|
||||||
python manage.py create_agent "Weather Reporter" "weather-reporter" api \
|
|
||||||
--category utilities --price 2.5 \
|
|
||||||
--api-base-url "https://api.openweathermap.org/data/2.5/weather" \
|
|
||||||
--api-key-env "OPENWEATHER_API_KEY" --auth-method query
|
|
||||||
```
|
|
||||||
|
|
||||||
### Agent Creation System Architecture
|
|
||||||
|
|
||||||
#### Core Framework (agent_base app)
|
|
||||||
- **BaseAgent Model**: Database catalog for agent marketplace
|
|
||||||
- **BaseAgentRequest/BaseAgentResponse**: Abstract models for tracking requests
|
|
||||||
- **StandardWebhookProcessor**: Handles N8N webhook integrations with message payload format
|
|
||||||
- **StandardAPIProcessor**: Handles direct API calls with flexible authentication methods
|
|
||||||
- **WebhookFormatDetector**: Utility to test and detect webhook formats
|
|
||||||
|
|
||||||
#### Template-Based Code Generation (✅ COMPLETE)
|
|
||||||
The system uses Django templates in `agent_base/templates/agent_generator/` to generate complete agent apps:
|
|
||||||
|
|
||||||
**Available Template Files:**
|
|
||||||
- `api_models.py` / `webhook_models.py`: Database models with custom fields
|
|
||||||
- `api_processor.py` / `webhook_processor.py` / `weather_api_processor.py`: Processor classes
|
|
||||||
- `views.py`: Django views with authentication and wallet integration
|
|
||||||
- `urls.py`: URL routing patterns with proper namespacing
|
|
||||||
- `admin.py`: Django admin configuration
|
|
||||||
- `apps.py`: Django app configuration
|
|
||||||
- `__init__.py`: App initialization
|
|
||||||
|
|
||||||
#### Supported Agent Types
|
|
||||||
|
|
||||||
**1. Webhook Agents (N8N Integration)**
|
|
||||||
- Uses `StandardWebhookProcessor` base class
|
|
||||||
- Message-based payload format: `{'message': {'text': 'content'}, 'sessionId': '...', 'userId': '...', 'agentId': '...'}`
|
|
||||||
- Automatic error handling and retry logic
|
|
||||||
- Processing time tracking
|
|
||||||
|
|
||||||
**2. API Agents (Direct Integration)**
|
|
||||||
- Uses `StandardAPIProcessor` base class
|
|
||||||
- Multiple authentication methods: bearer, api-key, basic, query
|
|
||||||
- GET/POST request support
|
|
||||||
- Response parsing and formatting
|
|
||||||
|
|
||||||
#### Example Agents (Production Ready)
|
|
||||||
|
|
||||||
All agents now feature consistent architecture with standardized themes, unified CSS, and isolated JavaScript utilities for container-like functionality.
|
|
||||||
|
|
||||||
**Data Analysis Agent** (Price: 5.00 AED):
|
|
||||||
- **N8N Integration**: PDF analysis webhook processor
|
|
||||||
- **File Upload**: PDF, CSV, Excel files with drag-and-drop interface
|
|
||||||
- **Real-time Results**: AJAX display with wallet balance updates
|
|
||||||
- **Features**: Summary/Detailed/Statistical analysis types
|
|
||||||
- **Form Submission Pattern**: Uses unified form submission (not button click)
|
|
||||||
- **Unified CSS**: Uses agent-base.css with professional theme
|
|
||||||
- **Text Display**: Simple text formatting (no complex markdown parsing)
|
|
||||||
- **Architecture**: Standard agent-container grid layout (1fr 350px) with proper wallet positioning
|
|
||||||
- **JavaScript Isolation**: DataAnalyzerUtils with agent-specific functionality
|
|
||||||
|
|
||||||
**Weather Reporter Agent** (Price: 2.00 AED):
|
|
||||||
- **API Integration**: OpenWeatherMap API with direct calls
|
|
||||||
- **Custom Fields**: location, report_type, temperature, humidity, wind_speed
|
|
||||||
- **Formatted Reports**: Both current and detailed weather reports
|
|
||||||
- **Real-time Results**: Dynamic display below form
|
|
||||||
- **Error Handling**: API failures and invalid locations
|
|
||||||
- **Unified CSS**: Uses agent-base.css with professional theme
|
|
||||||
- **Architecture**: Standard agent-container grid layout with proper structure
|
|
||||||
- **JavaScript Isolation**: WeatherUtils with agent-specific functionality
|
|
||||||
|
|
||||||
**Social Ads Generator Agent** (Price: 7.00 AED):
|
|
||||||
- **N8N Integration**: Social media ad generation via webhook
|
|
||||||
- **Platform Support**: Facebook, Instagram, LinkedIn, Twitter/X, TikTok, YouTube
|
|
||||||
- **Multi-language**: English, Arabic, Spanish, French, German, Chinese
|
|
||||||
- **Real-time Results**: Dynamic content generation and display
|
|
||||||
- **Unified CSS**: Uses agent-base.css with creative theme (glassmorphism)
|
|
||||||
- **Architecture**: Standard agent-container grid layout with glassmorphism styling
|
|
||||||
- **JavaScript Isolation**: SocialAdsUtils with agent-specific functionality
|
|
||||||
|
|
||||||
**Job Posting Generator Agent** (Price: 4.00 AED):
|
|
||||||
- **N8N Integration**: Professional job posting creation
|
|
||||||
- **Comprehensive Forms**: Job details, requirements, company info
|
|
||||||
- **Multi-language Support**: Multiple output languages
|
|
||||||
- **Enhanced UX**: Progressive form validation and real-time feedback
|
|
||||||
- **Unified CSS**: Uses agent-base.css with professional theme
|
|
||||||
- **Architecture**: Standard agent-container grid layout with professional styling
|
|
||||||
- **JavaScript Isolation**: JobPostingUtils with agent-specific functionality
|
|
||||||
|
|
||||||
**Five Whys Analysis Agent** (Price: 3.00 AED):
|
|
||||||
- **N8N Integration**: Problem analysis using Five Whys methodology
|
|
||||||
- **Comprehensive UX**: Enhanced UI with styled cards and professional layout
|
|
||||||
- **Multi-language Support**: Multiple output languages
|
|
||||||
- **Real-time Results**: Dynamic analysis generation and display
|
|
||||||
- **Unified CSS**: Uses agent-base.css with professional theme
|
|
||||||
- **Architecture**: Standard agent-container grid layout with consistent styling
|
|
||||||
- **JavaScript Isolation**: FiveWhysUtils with agent-specific functionality
|
|
||||||
|
|
||||||
### Management Commands
|
|
||||||
|
|
||||||
#### create_agent (✅ READY TO USE)
|
|
||||||
Generates complete agent apps with:
|
|
||||||
- Database models and migrations
|
|
||||||
- Processor classes (API/webhook/weather-specific)
|
|
||||||
- Django views with authentication and wallet integration
|
|
||||||
- URL routing with proper namespacing
|
|
||||||
- Admin interface with list views
|
|
||||||
- Custom field definitions based on agent type
|
|
||||||
- Simplified template structure: `agent_name/templates/detail.html`
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python manage.py create_agent --help
|
|
||||||
|
|
||||||
# Examples:
|
|
||||||
python manage.py create_agent "PDF Analyzer" "pdf-analyzer" api --price 5.0
|
|
||||||
python manage.py create_agent "Social Media Generator" "social-generator" webhook --price 3.0
|
|
||||||
```
|
|
||||||
|
|
||||||
#### test_webhook
|
|
||||||
Tests webhook endpoints to determine compatible formats:
|
|
||||||
```bash
|
|
||||||
# Test all formats
|
|
||||||
python manage.py test_webhook https://webhook.url
|
|
||||||
|
|
||||||
# Detect best format only
|
|
||||||
python manage.py test_webhook https://webhook.url --detect-best
|
|
||||||
```
|
|
||||||
|
|
||||||
### Manual Agent Creation (Legacy)
|
|
||||||
|
|
||||||
For custom agents requiring manual setup:
|
|
||||||
|
|
||||||
#### Step 1: Create Django App
|
|
||||||
```bash
|
|
||||||
python manage.py startapp agent_[name]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 2: Define Models
|
|
||||||
Extend `BaseAgentRequest` and `BaseAgentResponse` in `models.py`:
|
|
||||||
```python
|
|
||||||
from agent_base.models import BaseAgentRequest, BaseAgentResponse
|
|
||||||
|
|
||||||
class MyAgentRequest(BaseAgentRequest):
|
|
||||||
# Add agent-specific fields
|
|
||||||
input_text = models.TextField()
|
|
||||||
|
|
||||||
class MyAgentResponse(BaseAgentResponse):
|
|
||||||
request = models.OneToOneField(MyAgentRequest, on_delete=models.CASCADE, related_name='response')
|
|
||||||
output_text = models.TextField(blank=True)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 3: Create Processor
|
|
||||||
Choose between webhook or API processor:
|
|
||||||
|
|
||||||
**Webhook Processor:**
|
|
||||||
```python
|
|
||||||
from agent_base.processors import StandardWebhookProcessor
|
|
||||||
|
|
||||||
class MyAgentProcessor(StandardWebhookProcessor):
|
|
||||||
agent_slug = 'my-agent'
|
|
||||||
webhook_url = settings.N8N_WEBHOOK_MY_AGENT
|
|
||||||
agent_id = '123'
|
|
||||||
|
|
||||||
def prepare_message_text(self, **kwargs):
|
|
||||||
return f"Process: {kwargs.get('input_text')}"
|
|
||||||
```
|
|
||||||
|
|
||||||
**API Processor:**
|
|
||||||
```python
|
|
||||||
from agent_base.processors import StandardAPIProcessor
|
|
||||||
|
|
||||||
class MyAgentProcessor(StandardAPIProcessor):
|
|
||||||
agent_slug = 'my-agent'
|
|
||||||
api_base_url = 'https://api.example.com/v1/process'
|
|
||||||
api_key_env = 'MY_API_KEY'
|
|
||||||
auth_method = 'bearer'
|
|
||||||
|
|
||||||
def prepare_request_data(self, **kwargs):
|
|
||||||
return {'text': kwargs.get('input_text')}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 4: Add to Configuration
|
|
||||||
- Add app to `INSTALLED_APPS` in `settings.py`
|
|
||||||
- Add URL routing in `netcop_hub/urls.py`
|
|
||||||
- Run migrations: `python manage.py makemigrations && python manage.py migrate`
|
|
||||||
- Create BaseAgent entry in database
|
|
||||||
|
|
||||||
## Database Models Relationships
|
|
||||||
|
|
||||||
### Core Models
|
|
||||||
- `User` (authentication): Custom user with wallet functionality
|
|
||||||
- `BaseAgent` (agent_base): Agent catalog/marketplace entries
|
|
||||||
- `WalletTransaction` (wallet): Payment and usage tracking
|
|
||||||
|
|
||||||
### Agent-Specific Models
|
|
||||||
Each agent app has:
|
|
||||||
- `[Agent]Request`: Inherits from `BaseAgentRequest`, tracks user requests
|
|
||||||
- `[Agent]Response`: Inherits from `BaseAgentResponse`, stores AI responses
|
|
||||||
|
|
||||||
### Key Relationships
|
|
||||||
- `User` 1:N `BaseAgentRequest` (user can make multiple requests)
|
|
||||||
- `BaseAgent` 1:N `BaseAgentRequest` (agent can have multiple requests)
|
|
||||||
- `BaseAgentRequest` 1:1 `BaseAgentResponse` (each request has one response)
|
|
||||||
- `User` 1:N `WalletTransaction` (user has transaction history)
|
|
||||||
|
|
||||||
## URL Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
/ # Homepage (core app)
|
|
||||||
/auth/login/ # Authentication
|
|
||||||
/auth/register/ # User registration
|
|
||||||
/agents/[agent-slug]/ # Individual agent pages
|
|
||||||
/admin/ # Django admin
|
|
||||||
```
|
|
||||||
|
|
||||||
## Template Organization
|
|
||||||
|
|
||||||
Templates follow clean Django app structure:
|
|
||||||
- `templates/core/`: Homepage, marketplace, wallet (global templates)
|
|
||||||
- `templates/authentication/`: Login, registration (global templates)
|
|
||||||
- `[agent_name]/templates/[agent_name]/`: Individual agent templates within their respective apps (namespaced)
|
|
||||||
- `docs/`: All documentation and guides
|
|
||||||
- `tests/`: All test files
|
|
||||||
|
|
||||||
## Common Development Patterns
|
|
||||||
|
|
||||||
### Adding New Agent Fields
|
|
||||||
1. Add fields to agent request/response models
|
|
||||||
2. Update processor's `prepare_request_data()` method
|
|
||||||
3. Modify view's `process_request()` method
|
|
||||||
4. Update templates to include new fields
|
|
||||||
|
|
||||||
### Debugging Webhook Issues
|
|
||||||
1. Check webhook URL in `.env` file
|
|
||||||
2. Examine processor logs in console output
|
|
||||||
3. Verify JSON payload format in `prepare_request_data()`
|
|
||||||
4. Test webhook independently with tools like Postman
|
|
||||||
|
|
||||||
### Managing Agent Pricing
|
|
||||||
1. Update price in `populate_base_agents.py`
|
|
||||||
2. Run `python manage.py populate_base_agents` to update database
|
|
||||||
3. Pricing is enforced in `BaseAgentView.post()` method
|
|
||||||
|
|
||||||
## 💰 Wallet Management Best Practices (CRITICAL)
|
|
||||||
|
|
||||||
### ✅ CORRECT Wallet Deduction Pattern
|
|
||||||
**ALWAYS deduct wallet balance ONLY after successful processing, not before!**
|
|
||||||
|
|
||||||
#### View Layer (NO wallet deduction):
|
|
||||||
```python
|
|
||||||
# ❌ NEVER do this in views.py:
|
|
||||||
# request.user.deduct_balance(agent.price, description, agent_slug)
|
|
||||||
|
|
||||||
# ✅ CORRECT: Only check balance, create request object
|
|
||||||
if not request.user.has_sufficient_balance(agent.price):
|
|
||||||
return JsonResponse({'error': 'Insufficient wallet balance'}, status=400)
|
|
||||||
|
|
||||||
agent_request = MyAgentRequest.objects.create(
|
|
||||||
user=request.user,
|
|
||||||
agent=agent,
|
|
||||||
cost=agent.price,
|
|
||||||
# ... other fields
|
|
||||||
)
|
|
||||||
|
|
||||||
# Process request via processor
|
|
||||||
processor = MyAgentProcessor()
|
|
||||||
result = processor.process_request(request_obj=agent_request, ...)
|
|
||||||
|
|
||||||
# Return response with updated wallet balance
|
|
||||||
request.user.refresh_from_db()
|
|
||||||
return JsonResponse({
|
|
||||||
'success': True,
|
|
||||||
'request_id': str(agent_request.id),
|
|
||||||
'wallet_balance': float(request.user.wallet_balance) # Real-time balance
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Processor Layer (wallet deduction after success):
|
|
||||||
```python
|
|
||||||
def process_response(self, response_data, request_obj):
|
|
||||||
try:
|
|
||||||
# ... process response and determine success
|
|
||||||
success = response_data.get('status') == 'success' and bool(analysis_text)
|
|
||||||
|
|
||||||
# Create response object
|
|
||||||
response_obj = MyAgentResponse.objects.create(
|
|
||||||
request=request_obj,
|
|
||||||
success=success,
|
|
||||||
# ... other fields
|
|
||||||
)
|
|
||||||
|
|
||||||
# ✅ ONLY deduct wallet after successful processing
|
|
||||||
if success:
|
|
||||||
request_obj.user.deduct_balance(
|
|
||||||
request_obj.cost,
|
|
||||||
f"Agent Name - {description}",
|
|
||||||
'agent-slug'
|
|
||||||
)
|
|
||||||
print(f"Wallet deducted {request_obj.cost} AED for successful processing")
|
|
||||||
|
|
||||||
request_obj.status = 'completed' if success else 'failed'
|
|
||||||
request_obj.save()
|
|
||||||
|
|
||||||
return response_obj
|
|
||||||
except Exception as e:
|
|
||||||
# ✅ On error: NO wallet deduction, request marked as failed
|
|
||||||
request_obj.status = 'failed'
|
|
||||||
request_obj.save()
|
|
||||||
raise
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Frontend JavaScript (real-time balance updates):
|
|
||||||
```javascript
|
|
||||||
// Update wallet balance after successful processing
|
|
||||||
if (result.success && result.status === 'completed') {
|
|
||||||
// Update wallet balance display
|
|
||||||
if (result.wallet_balance !== undefined) {
|
|
||||||
updateWalletBalance(result.wallet_balance);
|
|
||||||
}
|
|
||||||
|
|
||||||
showToast('✅ Analysis completed and payment processed!', 'success');
|
|
||||||
} else if (result.status === 'failed') {
|
|
||||||
showToast('❌ Analysis failed - no charge applied', 'error');
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateWalletBalance(newBalance) {
|
|
||||||
// Update all wallet displays in real-time
|
|
||||||
document.querySelectorAll('[data-wallet-balance]').forEach(element => {
|
|
||||||
element.textContent = `${newBalance.toFixed(2)} AED`;
|
|
||||||
});
|
|
||||||
window.currentWalletBalance = newBalance;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔥 Critical Wallet Rules
|
|
||||||
1. **NEVER** deduct wallet in views.py before processing
|
|
||||||
2. **ALWAYS** deduct wallet in processor ONLY after `success=True`
|
|
||||||
3. **ALWAYS** return updated `wallet_balance` in JSON responses
|
|
||||||
4. **ALWAYS** update frontend wallet display in real-time
|
|
||||||
5. **ALWAYS** show clear user feedback: "payment processed" vs "no charge applied"
|
|
||||||
|
|
||||||
### Wallet Flow Summary
|
|
||||||
```
|
|
||||||
1. User uploads/submits → NO charge yet ✅
|
|
||||||
2. Create request object → NO charge yet ✅
|
|
||||||
3. Start processing → NO charge yet ✅
|
|
||||||
4. Processing succeeds → CHARGE NOW ✅
|
|
||||||
5. Update frontend → Show new balance ✅
|
|
||||||
6. If any step fails → NO charge at all ✅
|
|
||||||
```
|
|
||||||
|
|
||||||
This ensures users never lose money for failed processing while maintaining simple, efficient code.
|
|
||||||
|
|
||||||
## Payment System Architecture
|
|
||||||
|
|
||||||
### Stripe Integration (API-Based Verification)
|
|
||||||
|
|
||||||
The payment system uses **API-based verification** instead of webhooks for reliable, instant payment processing:
|
|
||||||
|
|
||||||
#### Payment Flow
|
|
||||||
```
|
|
||||||
1. User clicks "💳 Top Up Wallet"
|
|
||||||
2. Create Stripe checkout session via API
|
|
||||||
3. User completes payment on Stripe
|
|
||||||
4. Stripe redirects to success page with session_id
|
|
||||||
5. Success page verifies payment via Stripe API
|
|
||||||
6. Wallet balance updated immediately
|
|
||||||
7. Transaction recorded as "Wallet top-up via Stripe"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Key Components
|
|
||||||
- **StripePaymentHandler** (`wallet/stripe_handler.py`): Handles session creation and verification
|
|
||||||
- **Success Page Verification** (`core/views.py`): Automatic payment verification on return
|
|
||||||
- **Clean Transaction Descriptions**: Professional "Wallet top-up via Stripe" messages
|
|
||||||
- **No Webhook Dependency**: Reliable without webhook delivery issues
|
|
||||||
|
|
||||||
#### Configuration
|
|
||||||
```python
|
|
||||||
# settings.py
|
|
||||||
STRIPE_SECRET_KEY = 'sk_test_...' # From .env
|
|
||||||
STRIPE_PUBLISHABLE_KEY = 'pk_test_...' # From .env
|
|
||||||
STRIPE_WEBHOOK_SECRET = 'whsec_...' # Optional (backup)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Environment Variables
|
|
||||||
```bash
|
|
||||||
# .env
|
|
||||||
STRIPE_SECRET_KEY=sk_test_your_key_here
|
|
||||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_your_key_here
|
|
||||||
STRIPE_WEBHOOK_SECRET=whsec_your_secret_here # Optional
|
|
||||||
```
|
|
||||||
|
|
||||||
### Payment System URLs
|
|
||||||
- `/wallet/` - Wallet overview and transactions
|
|
||||||
- `/wallet/topup/` - Payment amount selection
|
|
||||||
- `/wallet/top-up/success/` - Payment verification and confirmation
|
|
||||||
- `/wallet/top-up/cancel/` - Payment cancellation
|
|
||||||
- `/stripe/debug/` - Stripe connectivity debugging (dev only)
|
|
||||||
|
|
||||||
### Advantages of API Verification
|
|
||||||
- **Instant confirmation** - No waiting for webhook delivery
|
|
||||||
- **Reliable** - No webhook delivery failures
|
|
||||||
- **Immediate user feedback** - Balance updates immediately
|
|
||||||
- **Simpler debugging** - You control the verification timing
|
|
||||||
- **Production-proven** - Used by many successful platforms
|
|
||||||
|
|
||||||
## Unified CSS and UI System
|
|
||||||
|
|
||||||
### Agent Styling Architecture
|
|
||||||
All agents now use a unified CSS system for consistent user experience and maintainability:
|
|
||||||
|
|
||||||
#### Core Files
|
|
||||||
- **`/static/css/agent-base.css`**: Unified component library for all agents
|
|
||||||
- **`/static/css/themes.css`**: Global color variables and themes
|
|
||||||
- **Agent-specific JavaScript utilities**: Each agent has isolated JavaScript functions for container-like functionality
|
|
||||||
|
|
||||||
### Agent Isolation Architecture
|
|
||||||
|
|
||||||
#### Container-like Functionality
|
|
||||||
All agents now implement true isolation to prevent cross-agent interference:
|
|
||||||
|
|
||||||
**JavaScript Isolation Pattern:**
|
|
||||||
```javascript
|
|
||||||
// Each agent has its own utility namespace
|
|
||||||
const DataAnalyzerUtils = { /* agent-specific functions */ };
|
|
||||||
const WeatherUtils = { /* agent-specific functions */ };
|
|
||||||
const SocialAdsUtils = { /* agent-specific functions */ };
|
|
||||||
const JobPostingUtils = { /* agent-specific functions */ };
|
|
||||||
const FiveWhysUtils = { /* agent-specific functions */ };
|
|
||||||
|
|
||||||
// For backward compatibility, each agent creates AgentUtils alias
|
|
||||||
const AgentUtils = DataAnalyzerUtils; // or appropriate agent utils
|
|
||||||
```
|
|
||||||
|
|
||||||
**Benefits of Isolation:**
|
|
||||||
- No shared dependencies between agents
|
|
||||||
- Changes to one agent don't affect others
|
|
||||||
- Agent-specific functionality can be customized
|
|
||||||
- Easier debugging and maintenance
|
|
||||||
- Container-like isolation without containerization complexity
|
|
||||||
|
|
||||||
#### Theme System
|
|
||||||
The unified CSS supports multiple themes via CSS custom properties:
|
|
||||||
|
|
||||||
1. **Professional Theme** (Default - Black & White):
|
|
||||||
- Used by: Job Posting Generator, Data Analyzer, Weather Reporter
|
|
||||||
- Clean, corporate appearance with subtle shadows
|
|
||||||
- Focused on readability and professional presentation
|
|
||||||
|
|
||||||
2. **Creative Theme** (Pink/Purple with Glassmorphism):
|
|
||||||
- Used by: Social Ads Generator
|
|
||||||
- Vibrant gradients and glassmorphism effects
|
|
||||||
- Enhanced visual appeal for creative content
|
|
||||||
|
|
||||||
3. **Minimal Theme** (Light Gray):
|
|
||||||
- Available for future agents requiring minimal design
|
|
||||||
- Subtle styling with maximum content focus
|
|
||||||
|
|
||||||
#### Implementation Pattern
|
|
||||||
```html
|
|
||||||
<!-- Standard agent template structure -->
|
|
||||||
<div class="agent-page theme-professional"> <!-- or theme-creative, theme-minimal -->
|
|
||||||
<div class="agent-container">
|
|
||||||
<div>
|
|
||||||
<!-- Main content area (first grid column) -->
|
|
||||||
<div class="card">
|
|
||||||
<h3 class="section-title">Agent Title</h3>
|
|
||||||
<!-- Agent form and content -->
|
|
||||||
</div>
|
|
||||||
<!-- Processing status and results stay within first grid column -->
|
|
||||||
</div>
|
|
||||||
<div class="wallet-section">
|
|
||||||
<!-- Wallet sidebar (second grid column) -->
|
|
||||||
<div class="card">
|
|
||||||
<h3 class="section-title">💳 Your Wallet</h3>
|
|
||||||
<!-- Wallet content -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Standardized Layout Architecture
|
|
||||||
|
|
||||||
**Grid Layout System:**
|
|
||||||
- `agent-container`: CSS Grid with `grid-template-columns: 1fr 350px`
|
|
||||||
- **First column**: Main content, forms, processing status, results
|
|
||||||
- **Second column**: Wallet sidebar (350px width)
|
|
||||||
- **Mobile responsive**: Single column on screens < 768px
|
|
||||||
|
|
||||||
**Critical Structure Rules:**
|
|
||||||
1. **Wallet positioning**: `wallet-section` must be a direct child of `agent-container` (separate grid column)
|
|
||||||
2. **Content hierarchy**: All agent content stays in first grid column
|
|
||||||
3. **Processing status**: Displays below form, spans full width of first column
|
|
||||||
4. **Results display**: Shows below processing status in first column
|
|
||||||
|
|
||||||
**Data Analyzer Wallet Fix Example:**
|
|
||||||
```html
|
|
||||||
<!-- ❌ INCORRECT: wallet-section inside main content -->
|
|
||||||
<div class="agent-container">
|
|
||||||
<div>
|
|
||||||
<form>...</form>
|
|
||||||
<div class="wallet-section">...</div> <!-- Wrong placement -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ✅ CORRECT: wallet-section as separate grid column -->
|
|
||||||
<div class="agent-container">
|
|
||||||
<div>
|
|
||||||
<form>...</form>
|
|
||||||
<!-- Processing Status -->
|
|
||||||
<!-- Results -->
|
|
||||||
</div>
|
|
||||||
<div class="wallet-section">...</div> <!-- Correct placement -->
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Text Display Standardization
|
|
||||||
|
|
||||||
#### Simple Text Formatting Approach
|
|
||||||
After testing complex markdown parsing, the system now uses simplified text formatting for better reliability:
|
|
||||||
|
|
||||||
**Current Implementation:**
|
|
||||||
```javascript
|
|
||||||
// Simple text formatting in AgentUtils.parseMarkdown()
|
|
||||||
parseMarkdown(text) {
|
|
||||||
if (!text) return '';
|
|
||||||
return text
|
|
||||||
.replace(/\*\*/g, '') // Remove markdown bold syntax
|
|
||||||
.replace(/\#{1,3}\s/g, '') // Remove header syntax
|
|
||||||
.replace(/\n{3,}/g, '\n\n') // Reduce excessive line breaks
|
|
||||||
.replace(/\n/g, '<br>') // Convert line breaks to HTML
|
|
||||||
.trim();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Benefits:**
|
|
||||||
- No external dependencies (removed Marked.js + DOMPurify)
|
|
||||||
- Consistent formatting across all agents
|
|
||||||
- No risk of layout breaking from complex markdown
|
|
||||||
- Fast rendering and simple maintenance
|
|
||||||
|
|
||||||
#### CSS Text Styling
|
|
||||||
```css
|
|
||||||
.results-content {
|
|
||||||
line-height: 1.6;
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
white-space: pre-line; /* Preserves line breaks */
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Form Submission Standardization
|
|
||||||
|
|
||||||
All agents now use consistent form submission patterns:
|
|
||||||
|
|
||||||
#### Unified Pattern
|
|
||||||
```javascript
|
|
||||||
// Standard form submission handler
|
|
||||||
document.getElementById('agentForm').addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
// Validation, authentication, and balance checks
|
|
||||||
if (!isFormValid()) return;
|
|
||||||
|
|
||||||
// Submit via FormData with CSRF token (automatic inclusion)
|
|
||||||
const formData = new FormData(this);
|
|
||||||
|
|
||||||
fetch(window.location.href, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(result => {
|
|
||||||
// Handle polling or immediate response
|
|
||||||
if (result.success && result.request_id) {
|
|
||||||
pollForResults(result.request_id);
|
|
||||||
} else {
|
|
||||||
displayResults(result);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Key Improvements
|
|
||||||
- **Form submission** instead of button click handlers
|
|
||||||
- **Automatic CSRF handling** via FormData(form)
|
|
||||||
- **Consistent error handling** across all agents
|
|
||||||
- **Unified polling mechanism** for webhook-based agents
|
|
||||||
|
|
||||||
## Current Architecture (Clean & Modern)
|
|
||||||
|
|
||||||
The project uses a clean, modular individual agent architecture:
|
|
||||||
|
|
||||||
### Current System Features
|
|
||||||
- **Individual agent apps**: Each agent is a separate Django app (`weather_reporter/`, etc.)
|
|
||||||
- **Clean template organization**: Templates live within their respective agent apps
|
|
||||||
- **Organized project structure**: Documentation in `docs/`, tests in `tests/`, clean root directory
|
|
||||||
- **BaseAgent catalog system**: Centralized marketplace with individual agent implementations
|
|
||||||
- **Modular processors**: Each agent has its own processor for API/webhook integration
|
|
||||||
- **App-specific templates**: `agent_name/templates/agent_name/detail.html` (namespaced to prevent conflicts)
|
|
||||||
|
|
||||||
### Best Practices
|
|
||||||
|
|
||||||
#### Agent Development Standards
|
|
||||||
- **Individual App Architecture**: Each agent is a separate Django app
|
|
||||||
- **Template Organization**: Place templates within agent app (`agent_name/templates/agent_name/`)
|
|
||||||
- **Automated Creation**: Use `create_agent` command for initial setup
|
|
||||||
- **Clean Structure**: Keep root directory organized with `docs/` and `tests/` folders
|
|
||||||
|
|
||||||
#### Modern Agent Features (Required)
|
|
||||||
- **Real-time Results Display**: Use AJAX to show results below form without page reload
|
|
||||||
- **Wallet Balance Updates**: Update balance displays immediately after successful processing
|
|
||||||
- **Data Attributes**: Add `data-wallet-balance` to all balance elements for easy targeting
|
|
||||||
- **Continuous Workflow**: Allow multiple requests without page refresh ("Get Another" functionality)
|
|
||||||
- **Clear User Feedback**: Show "payment processed" vs "no charge applied" messages
|
|
||||||
- **Standardized Layout**: Use agent-container grid layout (1fr 350px) with proper wallet positioning
|
|
||||||
- **Theme Consistency**: Apply unified CSS themes across all agents
|
|
||||||
- **JavaScript Isolation**: Agent-specific utilities for container-like functionality
|
|
||||||
|
|
||||||
#### Recent Standardization Improvements (2024)
|
|
||||||
|
|
||||||
**Agent Architecture Consistency:**
|
|
||||||
All 5 production agents now follow standardized patterns:
|
|
||||||
1. **Data Analyzer**: Reduced custom CSS from 400+ lines to ~78 lines, standardized HTML structure
|
|
||||||
2. **Job Posting Generator**: Enhanced with professional theme and proper grid layout
|
|
||||||
3. **Five Whys Analyzer**: Applied black and white theme with consistent styling
|
|
||||||
4. **Social Ads Generator**: Maintained creative theme while standardizing structure
|
|
||||||
5. **Weather Reporter**: Professional theme with clean weather data presentation
|
|
||||||
|
|
||||||
**Key Improvements Made:**
|
|
||||||
- **HTML Structure**: All agents use standard `agent-container` grid layout
|
|
||||||
- **CSS Consolidation**: Removed duplicate styles, standardized on `agent-base.css`
|
|
||||||
- **Wallet Positioning**: Fixed wallet appearing at bottom vs. right side across all agents
|
|
||||||
- **JavaScript Isolation**: Each agent has isolated utilities (DataAnalyzerUtils, WeatherUtils, etc.)
|
|
||||||
- **Theme Application**: Consistent theme implementation across all agents
|
|
||||||
- **Code Reduction**: Eliminated 400+ lines of redundant CSS code
|
|
||||||
|
|
||||||
#### Frontend JavaScript Requirements
|
|
||||||
```javascript
|
|
||||||
// Required functions for all agents:
|
|
||||||
- updateWalletBalance(newBalance) // Updates all balance displays
|
|
||||||
- displayResults(result) // Shows results below form
|
|
||||||
- pollForResults(requestId) // Checks processing status
|
|
||||||
- resetForm() // Prepares for next request
|
|
||||||
```
|
|
||||||
|
|
||||||
#### CSRF Token Requirements
|
|
||||||
All agent templates that use JavaScript form submission must include:
|
|
||||||
```html
|
|
||||||
<!-- Required: Add CSRF token to template -->
|
|
||||||
{% csrf_token %}
|
|
||||||
```
|
|
||||||
|
|
||||||
For manual FormData submission (like data analyzer), access token via:
|
|
||||||
```javascript
|
|
||||||
// For templates with {% csrf_token %} tag
|
|
||||||
formData.append('csrfmiddlewaretoken', document.querySelector('[name=csrfmiddlewaretoken]').value);
|
|
||||||
|
|
||||||
// For HTML forms with {% csrf_token %} inside form
|
|
||||||
const formData = new FormData(this); // 'this' refers to form element - automatically includes CSRF
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Template Requirements
|
|
||||||
```html
|
|
||||||
<!-- Required data attributes for wallet balance -->
|
|
||||||
<span data-wallet-balance>{{ user.wallet_balance|floatformat:2 }} AED</span>
|
|
||||||
<div data-wallet-balance>{{ user.wallet_balance|floatformat:2 }} AED</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔐 Password Reset System (Implemented July 2024)
|
|
||||||
|
|
||||||
### Overview
|
|
||||||
A comprehensive forgot password system has been implemented with secure token-based authentication, professional UI, and Railway deployment support.
|
|
||||||
|
|
||||||
### Key Features
|
|
||||||
- **Secure Token System**: UUID-based tokens with 1-hour expiration
|
|
||||||
- **Email Integration**: Gmail SMTP with production-ready configuration
|
|
||||||
- **Professional UI**: Responsive design matching existing authentication pages
|
|
||||||
- **Railway Deployment**: Automatic environment detection and proper URL generation
|
|
||||||
- **User Experience**: Clear error messages and helpful navigation
|
|
||||||
- **Security**: Single-use tokens, no email enumeration, comprehensive logging
|
|
||||||
|
|
||||||
### Database Schema
|
|
||||||
```python
|
|
||||||
class PasswordResetToken(models.Model):
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='password_reset_tokens')
|
|
||||||
token = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
expires_at = models.DateTimeField()
|
|
||||||
is_used = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
return not self.is_used and timezone.now() < self.expires_at
|
|
||||||
|
|
||||||
def mark_as_used(self):
|
|
||||||
self.is_used = True
|
|
||||||
self.save()
|
|
||||||
```
|
|
||||||
|
|
||||||
### URL Configuration
|
|
||||||
```python
|
|
||||||
# authentication/urls.py
|
|
||||||
urlpatterns = [
|
|
||||||
path('forgot-password/', views.forgot_password_view, name='forgot_password'),
|
|
||||||
path('reset-password/<uuid:token>/', views.reset_password_view, name='reset_password'),
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### User Experience Flow
|
|
||||||
1. **Request Reset**: User clicks "Forgot your password?" on login page
|
|
||||||
2. **Email Validation**: System shows helpful error if user doesn't exist
|
|
||||||
3. **Token Generation**: Secure UUID token created with 1-hour expiration
|
|
||||||
4. **Email Delivery**: Professional email sent with reset instructions
|
|
||||||
5. **Password Reset**: User clicks link, enters new password
|
|
||||||
6. **Completion**: Token marked as used, user redirected to login
|
|
||||||
|
|
||||||
### Railway Deployment Configuration
|
|
||||||
```python
|
|
||||||
# Automatic environment detection
|
|
||||||
if config('RAILWAY_ENVIRONMENT', default=''):
|
|
||||||
SITE_URL = 'https://netcop.up.railway.app'
|
|
||||||
else:
|
|
||||||
SITE_URL = config('SITE_URL', default='http://localhost:8000')
|
|
||||||
```
|
|
||||||
|
|
||||||
### Required Environment Variables (Railway)
|
|
||||||
```bash
|
|
||||||
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
|
|
||||||
EMAIL_HOST=smtp.gmail.com
|
|
||||||
EMAIL_PORT=587
|
|
||||||
EMAIL_USE_TLS=True
|
|
||||||
EMAIL_HOST_USER=your-email@gmail.com
|
|
||||||
EMAIL_HOST_PASSWORD=your-gmail-app-password
|
|
||||||
DEFAULT_FROM_EMAIL=NetCop <your-email@gmail.com>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Security Features
|
|
||||||
- **Token Expiration**: All tokens expire after 1 hour
|
|
||||||
- **Single-Use**: Tokens are marked as used after password reset
|
|
||||||
- **No Email Enumeration**: Helpful error messages without revealing account existence
|
|
||||||
- **Secure URLs**: HTTPS links in production environment
|
|
||||||
- **Logging**: Comprehensive error logging for debugging
|
|
||||||
|
|
||||||
### Error Handling
|
|
||||||
- **Clear Messages**: "No account found with email X. Please check your email or create account"
|
|
||||||
- **Helpful Navigation**: Direct links to registration page
|
|
||||||
- **Email Failures**: Detailed error messages for debugging
|
|
||||||
- **Token Validation**: Proper handling of expired/invalid tokens
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
```bash
|
|
||||||
# Test email configuration
|
|
||||||
python manage.py test_email --email=user@example.com
|
|
||||||
|
|
||||||
# Manual testing flow
|
|
||||||
1. Go to /auth/forgot-password/
|
|
||||||
2. Enter registered user email
|
|
||||||
3. Check email inbox (including spam)
|
|
||||||
4. Click reset link
|
|
||||||
5. Set new password
|
|
||||||
6. Login with new credentials
|
|
||||||
```
|
|
||||||
|
|
||||||
### Files Created/Modified
|
|
||||||
- `authentication/models.py` - Added PasswordResetToken model
|
|
||||||
- `authentication/views.py` - Added forgot_password_view and reset_password_view
|
|
||||||
- `authentication/urls.py` - Added password reset URL patterns
|
|
||||||
- `templates/authentication/forgot_password.html` - Professional forgot password form
|
|
||||||
- `templates/authentication/reset_password.html` - Password reset form
|
|
||||||
- `templates/authentication/login.html` - Added forgot password link
|
|
||||||
- `netcop_hub/settings.py` - Email and site URL configuration
|
|
||||||
- `authentication/management/commands/test_email.py` - Email testing utility
|
|
||||||
- `docs/FORGOT_PASSWORD_IMPLEMENTATION.md` - Comprehensive documentation
|
|
||||||
|
|
||||||
### Common Issues & Solutions
|
|
||||||
1. **Email not received**: Check spam folder, verify Railway environment variables
|
|
||||||
2. **Link not working**: Ensure SITE_URL is correctly configured for Railway
|
|
||||||
3. **Token expired**: Tokens expire after 1 hour, request new reset
|
|
||||||
4. **User not found**: Register user first, then request password reset
|
|
||||||
|
|
||||||
### Implementation Notes
|
|
||||||
- The system uses Django's built-in password validation
|
|
||||||
- Email templates are plain text for maximum compatibility
|
|
||||||
- Token cleanup can be implemented via periodic task if needed
|
|
||||||
- System is production-ready and deployed on Railway
|
|
||||||
|
|
||||||
This implementation provides a secure, user-friendly password reset system that integrates seamlessly with the existing NetCop authentication flow.
|
|
||||||
@ -1,714 +0,0 @@
|
|||||||
# Development Guide - Enhanced with Agent Testing
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
### Option 1: Use the Development Script (Recommended)
|
|
||||||
```bash
|
|
||||||
./run_dev.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Option 2: Manual Startup
|
|
||||||
```bash
|
|
||||||
# Clear any interfering environment variables
|
|
||||||
unset DATABASE_URL
|
|
||||||
|
|
||||||
# Activate virtual environment
|
|
||||||
source venv/bin/activate
|
|
||||||
|
|
||||||
# Start server
|
|
||||||
python manage.py runserver
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Issues
|
|
||||||
|
|
||||||
### Issue: "Connection refused" Error with PostgreSQL
|
|
||||||
**Cause:** You have `DATABASE_URL` set as an environment variable pointing to PostgreSQL.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
```bash
|
|
||||||
# Check if DATABASE_URL is set
|
|
||||||
echo $DATABASE_URL
|
|
||||||
|
|
||||||
# Temporarily unset it
|
|
||||||
unset DATABASE_URL
|
|
||||||
|
|
||||||
# Start server
|
|
||||||
python manage.py runserver
|
|
||||||
```
|
|
||||||
|
|
||||||
**Permanent Fix:**
|
|
||||||
If `DATABASE_URL` keeps getting set, check these files:
|
|
||||||
- `~/.bashrc`
|
|
||||||
- `~/.bash_profile`
|
|
||||||
- `~/.profile`
|
|
||||||
- `~/.zshrc`
|
|
||||||
- `~/.env` (global)
|
|
||||||
|
|
||||||
Remove any lines containing `DATABASE_URL=` unless you specifically need them.
|
|
||||||
|
|
||||||
### Issue: Database Tables Don't Exist
|
|
||||||
```bash
|
|
||||||
# Run migrations
|
|
||||||
python manage.py migrate
|
|
||||||
|
|
||||||
# Create admin user and populate data
|
|
||||||
python manage.py populate_agents --create-admin
|
|
||||||
```
|
|
||||||
|
|
||||||
### Issue: Admin Login Not Working
|
|
||||||
```bash
|
|
||||||
# Check if admin user exists
|
|
||||||
python manage.py backup_users --action info
|
|
||||||
|
|
||||||
# Create admin user
|
|
||||||
python manage.py create_user admin@example.com password123 --superuser
|
|
||||||
```
|
|
||||||
|
|
||||||
## Database Configuration
|
|
||||||
|
|
||||||
### Local Development (Default)
|
|
||||||
- **Engine:** SQLite
|
|
||||||
- **Location:** `db.sqlite3`
|
|
||||||
- **Setup:** None required
|
|
||||||
|
|
||||||
### Local Development with PostgreSQL (Optional)
|
|
||||||
1. **Set up PostgreSQL:**
|
|
||||||
```bash
|
|
||||||
# Using Docker (easiest)
|
|
||||||
docker run --name netcop-postgres \\
|
|
||||||
-e POSTGRES_DB=netcop_hub \\
|
|
||||||
-e POSTGRES_USER=netcop_user \\
|
|
||||||
-e POSTGRES_PASSWORD=netcop_pass \\
|
|
||||||
-p 5432:5432 -d postgres:15
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Enable in .env:**
|
|
||||||
```env
|
|
||||||
USE_POSTGRESQL=True
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Run migrations:**
|
|
||||||
```bash
|
|
||||||
python manage.py migrate
|
|
||||||
python manage.py populate_agents --create-admin
|
|
||||||
```
|
|
||||||
|
|
||||||
### Railway Production
|
|
||||||
- **Engine:** PostgreSQL (automatic)
|
|
||||||
- **Configuration:** Via Railway's `DATABASE_URL`
|
|
||||||
- **Setup:** None required
|
|
||||||
|
|
||||||
## Environment Variables
|
|
||||||
|
|
||||||
### Required for Development
|
|
||||||
```env
|
|
||||||
SECRET_KEY=your-secret-key-here
|
|
||||||
DEBUG=True
|
|
||||||
ALLOWED_HOSTS=localhost,127.0.0.1
|
|
||||||
CSRF_TRUSTED_ORIGINS=http://localhost:8000,http://127.0.0.1:8000
|
|
||||||
```
|
|
||||||
|
|
||||||
### Optional for Development
|
|
||||||
```env
|
|
||||||
# Force PostgreSQL (requires PostgreSQL setup)
|
|
||||||
USE_POSTGRESQL=True
|
|
||||||
|
|
||||||
# Or specify exact database URL
|
|
||||||
DATABASE_URL=postgresql://netcop_user:netcop_pass@localhost:5432/netcop_hub
|
|
||||||
|
|
||||||
# API Keys (for full functionality)
|
|
||||||
OPENWEATHER_API_KEY=your-key-here
|
|
||||||
STRIPE_SECRET_KEY=sk_test_...
|
|
||||||
STRIPE_WEBHOOK_SECRET=whsec_...
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development Workflow
|
|
||||||
|
|
||||||
### Daily Development
|
|
||||||
```bash
|
|
||||||
# Start development server
|
|
||||||
./run_dev.sh
|
|
||||||
|
|
||||||
# In another terminal - run commands
|
|
||||||
source venv/bin/activate
|
|
||||||
python manage.py check_db # Check database status
|
|
||||||
python manage.py makemigrations # Create migrations
|
|
||||||
python manage.py migrate # Apply migrations
|
|
||||||
```
|
|
||||||
|
|
||||||
### Testing Changes
|
|
||||||
```bash
|
|
||||||
# Check for issues
|
|
||||||
python manage.py check
|
|
||||||
|
|
||||||
# Test migrations
|
|
||||||
python manage.py migrate --plan
|
|
||||||
|
|
||||||
# Create test data
|
|
||||||
python manage.py populate_agents --create-admin
|
|
||||||
```
|
|
||||||
|
|
||||||
### Debugging
|
|
||||||
```bash
|
|
||||||
# Check database configuration
|
|
||||||
python manage.py check_db
|
|
||||||
|
|
||||||
# Check migration status
|
|
||||||
python manage.py showmigrations
|
|
||||||
|
|
||||||
# Django shell
|
|
||||||
python manage.py shell
|
|
||||||
```
|
|
||||||
|
|
||||||
## File Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
netcop_django/
|
|
||||||
├── run_dev.sh # Development startup script
|
|
||||||
├── manage.py # Django management
|
|
||||||
├── requirements.txt # Python dependencies
|
|
||||||
├── .env # Local environment variables
|
|
||||||
├── db.sqlite3 # SQLite database (local)
|
|
||||||
├── docs/ # Documentation
|
|
||||||
├── static/ # Static files
|
|
||||||
├── templates/ # Global templates
|
|
||||||
├── netcop_hub/ # Django project settings
|
|
||||||
├── core/ # Main app (homepage, marketplace)
|
|
||||||
├── authentication/ # User management
|
|
||||||
├── wallet/ # Payment system
|
|
||||||
├── agent_base/ # Agent framework
|
|
||||||
├── weather_reporter/ # Weather agent
|
|
||||||
├── data_analyzer/ # Data analysis agent
|
|
||||||
├── job_posting_generator/ # Job posting agent
|
|
||||||
└── social_ads_generator/ # Social ads agent
|
|
||||||
```
|
|
||||||
|
|
||||||
## Useful Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Development
|
|
||||||
./run_dev.sh # Start dev server
|
|
||||||
python manage.py check_db # Check database
|
|
||||||
python manage.py migrate # Run migrations
|
|
||||||
python manage.py populate_agents --create-admin # Setup data
|
|
||||||
|
|
||||||
# User Management
|
|
||||||
python manage.py create_user email@example.com password123 --superuser
|
|
||||||
python manage.py backup_users --action info
|
|
||||||
|
|
||||||
# Database Management
|
|
||||||
python manage.py reset_database --action full --confirm
|
|
||||||
python manage.py fix_migrations --app data_analyzer
|
|
||||||
|
|
||||||
# Debugging
|
|
||||||
python manage.py check # System check
|
|
||||||
python manage.py showmigrations # Migration status
|
|
||||||
python manage.py shell # Django shell
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Server Won't Start
|
|
||||||
1. Check if `DATABASE_URL` is set: `echo $DATABASE_URL`
|
|
||||||
2. Unset it: `unset DATABASE_URL`
|
|
||||||
3. Use the development script: `./run_dev.sh`
|
|
||||||
|
|
||||||
### Database Issues
|
|
||||||
1. Check configuration: `python manage.py check_db`
|
|
||||||
2. Run migrations: `python manage.py migrate`
|
|
||||||
3. Reset if needed: `python manage.py reset_database --action full --confirm`
|
|
||||||
|
|
||||||
### Import Errors
|
|
||||||
1. Activate virtual environment: `source venv/bin/activate`
|
|
||||||
2. Install requirements: `pip install -r requirements.txt`
|
|
||||||
|
|
||||||
### Permission Errors
|
|
||||||
1. Make script executable: `chmod +x run_dev.sh`
|
|
||||||
2. Check file permissions: `ls -la`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 Agent Testing Procedures *(5 Whys Experience)*
|
|
||||||
|
|
||||||
Based on extensive debugging and the successful 5 Whys Analyzer implementation, here are comprehensive testing procedures for error-free agent development.
|
|
||||||
|
|
||||||
### Pre-Development Agent Testing Setup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Agent validation environment setup
|
|
||||||
python manage.py shell -c "
|
|
||||||
from agent_base.models import BaseAgent
|
|
||||||
from django.template.loader import get_template
|
|
||||||
from django.urls import reverse
|
|
||||||
import os
|
|
||||||
|
|
||||||
def validate_agent_environment(agent_slug):
|
|
||||||
print(f'🧪 Testing environment for {agent_slug}...')
|
|
||||||
|
|
||||||
# Test 1: BaseAgent exists
|
|
||||||
try:
|
|
||||||
agent = BaseAgent.objects.get(slug=agent_slug)
|
|
||||||
print(f'✅ BaseAgent found: {agent.name}')
|
|
||||||
except BaseAgent.DoesNotExist:
|
|
||||||
print(f'❌ BaseAgent not found for slug: {agent_slug}')
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Test 2: URL resolution
|
|
||||||
try:
|
|
||||||
url = reverse('core:agent_detail', args=[agent_slug])
|
|
||||||
print(f'✅ URL resolved: {url}')
|
|
||||||
except Exception as e:
|
|
||||||
print(f'❌ URL resolution failed: {e}')
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Test 3: Template loading
|
|
||||||
try:
|
|
||||||
template = get_template(f'{agent_slug.replace(\"-\", \"_\")}/detail.html')
|
|
||||||
print(f'✅ Template found: {template.origin.name}')
|
|
||||||
except Exception as e:
|
|
||||||
print(f'❌ Template not found: {e}')
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Test 4: Environment variables (if needed)
|
|
||||||
env_var = f'N8N_WEBHOOK_{agent_slug.upper().replace(\"-\", \"_\")}'
|
|
||||||
if os.getenv(env_var):
|
|
||||||
print(f'✅ Environment variable found: {env_var}')
|
|
||||||
else:
|
|
||||||
print(f'⚠️ Environment variable not set: {env_var}')
|
|
||||||
|
|
||||||
print(f'🎯 Environment validation complete for {agent_slug}')
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Test your agent
|
|
||||||
validate_agent_environment('five-whys-analyzer')
|
|
||||||
"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Agent Request Lifecycle Testing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Test complete agent request lifecycle
|
|
||||||
python manage.py shell -c "
|
|
||||||
import uuid
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from agent_base.models import BaseAgent
|
|
||||||
from five_whys_analyzer.models import FiveWhysAnalyzerRequest, FiveWhysAnalyzerResponse
|
|
||||||
from five_whys_analyzer.processor import FiveWhysAnalyzerProcessor
|
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
def test_agent_lifecycle(agent_slug='five-whys-analyzer'):
|
|
||||||
print(f'🧪 Testing complete lifecycle for {agent_slug}...')
|
|
||||||
|
|
||||||
# Get test user
|
|
||||||
user = User.objects.filter(is_superuser=True).first()
|
|
||||||
if not user:
|
|
||||||
print('❌ No superuser found for testing')
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Test 1: Agent exists and is active
|
|
||||||
try:
|
|
||||||
agent = BaseAgent.objects.get(slug=agent_slug, is_active=True)
|
|
||||||
print(f'✅ Active agent found: {agent.name} (${agent.price})')
|
|
||||||
except BaseAgent.DoesNotExist:
|
|
||||||
print(f'❌ Active agent not found: {agent_slug}')
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Test 2: User has sufficient balance
|
|
||||||
if user.wallet_balance < agent.price:
|
|
||||||
print(f'⚠️ User balance ({user.wallet_balance}) < agent price ({agent.price})')
|
|
||||||
print('Adding test balance...')
|
|
||||||
user.wallet_balance += Decimal('50.00')
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
# Test 3: Create request object
|
|
||||||
session_id = str(uuid.uuid4())
|
|
||||||
try:
|
|
||||||
request_obj = FiveWhysAnalyzerRequest.objects.create(
|
|
||||||
user=user,
|
|
||||||
agent=agent,
|
|
||||||
session_id=session_id,
|
|
||||||
cost=Decimal('8.00'),
|
|
||||||
problem_statement='Test problem for validation',
|
|
||||||
status='pending'
|
|
||||||
)
|
|
||||||
print(f'✅ Request created: {request_obj.id}')
|
|
||||||
except Exception as e:
|
|
||||||
print(f'❌ Request creation failed: {e}')
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Test 4: Status transitions
|
|
||||||
try:
|
|
||||||
request_obj.status = 'processing'
|
|
||||||
request_obj.save()
|
|
||||||
print('✅ Status updated to processing')
|
|
||||||
|
|
||||||
request_obj.status = 'completed'
|
|
||||||
request_obj.save()
|
|
||||||
print('✅ Status updated to completed')
|
|
||||||
except Exception as e:
|
|
||||||
print(f'❌ Status update failed: {e}')
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Test 5: Response creation
|
|
||||||
try:
|
|
||||||
response_obj = FiveWhysAnalyzerResponse.objects.create(
|
|
||||||
request=request_obj,
|
|
||||||
success=True,
|
|
||||||
final_report='Test report generated successfully',
|
|
||||||
processing_time=2.5
|
|
||||||
)
|
|
||||||
print(f'✅ Response created: {response_obj.id}')
|
|
||||||
except Exception as e:
|
|
||||||
print(f'❌ Response creation failed: {e}')
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Test 6: Cleanup
|
|
||||||
response_obj.delete()
|
|
||||||
request_obj.delete()
|
|
||||||
print('✅ Test objects cleaned up')
|
|
||||||
|
|
||||||
print(f'🎯 Lifecycle test completed successfully for {agent_slug}')
|
|
||||||
return True
|
|
||||||
|
|
||||||
# Run the test
|
|
||||||
test_agent_lifecycle()
|
|
||||||
"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Wallet Integration Testing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Test wallet integration patterns (5 Whys delayed deduction pattern)
|
|
||||||
python manage.py shell -c "
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
from agent_base.models import BaseAgent
|
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
def test_wallet_integration():
|
|
||||||
print('🧪 Testing wallet integration patterns...')
|
|
||||||
|
|
||||||
user = User.objects.filter(is_superuser=True).first()
|
|
||||||
agent = BaseAgent.objects.filter(is_active=True).first()
|
|
||||||
|
|
||||||
if not user or not agent:
|
|
||||||
print('❌ Missing test user or agent')
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Record initial balance
|
|
||||||
initial_balance = user.wallet_balance
|
|
||||||
print(f'Initial balance: {initial_balance}')
|
|
||||||
|
|
||||||
# Test 1: Balance check (5 Whys pattern)
|
|
||||||
if user.wallet_balance >= agent.price:
|
|
||||||
print('✅ Sufficient balance for processing')
|
|
||||||
else:
|
|
||||||
print('❌ Insufficient balance')
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Test 2: Delayed deduction simulation
|
|
||||||
print('🔄 Simulating processing...')
|
|
||||||
processing_success = True # Simulate success
|
|
||||||
|
|
||||||
if processing_success:
|
|
||||||
# Only deduct after success (5 Whys pattern)
|
|
||||||
user.deduct_balance(
|
|
||||||
agent.price,
|
|
||||||
f'Test deduction for {agent.name}',
|
|
||||||
agent.slug
|
|
||||||
)
|
|
||||||
print(f'✅ Balance deducted after success: {user.wallet_balance}')
|
|
||||||
|
|
||||||
# Verify deduction
|
|
||||||
expected_balance = initial_balance - agent.price
|
|
||||||
if user.wallet_balance == expected_balance:
|
|
||||||
print('✅ Wallet deduction verified correct')
|
|
||||||
else:
|
|
||||||
print(f'❌ Wallet deduction incorrect: expected {expected_balance}, got {user.wallet_balance}')
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
print('✅ No deduction for failed processing (correct behavior)')
|
|
||||||
|
|
||||||
# Test 3: Restore balance for other tests
|
|
||||||
user.wallet_balance = initial_balance
|
|
||||||
user.save()
|
|
||||||
print(f'🔄 Balance restored to: {user.wallet_balance}')
|
|
||||||
|
|
||||||
print('🎯 Wallet integration test completed successfully')
|
|
||||||
return True
|
|
||||||
|
|
||||||
test_wallet_integration()
|
|
||||||
"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Template and URL Testing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Test template loading and URL routing (common 5 Whys issues)
|
|
||||||
python manage.py shell -c "
|
|
||||||
from django.template.loader import get_template
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.test import RequestFactory
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
def test_template_and_urls():
|
|
||||||
print('🧪 Testing templates and URLs...')
|
|
||||||
|
|
||||||
# Test template loading for all agents
|
|
||||||
agents = ['weather_reporter', 'five_whys_analyzer']
|
|
||||||
|
|
||||||
for agent in agents:
|
|
||||||
try:
|
|
||||||
template = get_template(f'{agent}/detail.html')
|
|
||||||
print(f'✅ Template loaded for {agent}: {template.origin.name}')
|
|
||||||
except Exception as e:
|
|
||||||
print(f'❌ Template failed for {agent}: {e}')
|
|
||||||
|
|
||||||
# Test URL resolution
|
|
||||||
url_tests = [
|
|
||||||
('core:homepage', []),
|
|
||||||
('core:marketplace', []),
|
|
||||||
('core:wallet', []),
|
|
||||||
('core:agent_detail', ['weather-reporter']),
|
|
||||||
('core:agent_detail', ['five-whys-analyzer']),
|
|
||||||
]
|
|
||||||
|
|
||||||
for url_name, args in url_tests:
|
|
||||||
try:
|
|
||||||
url = reverse(url_name, args=args)
|
|
||||||
print(f'✅ URL resolved {url_name}: {url}')
|
|
||||||
except Exception as e:
|
|
||||||
print(f'❌ URL failed {url_name}: {e}')
|
|
||||||
|
|
||||||
print('🎯 Template and URL testing completed')
|
|
||||||
|
|
||||||
test_template_and_urls()
|
|
||||||
"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Error Handling Testing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Test error handling patterns (5 Whys comprehensive error handling)
|
|
||||||
python manage.py shell -c "
|
|
||||||
from five_whys_analyzer.processor import FiveWhysAnalyzerProcessor
|
|
||||||
from agent_base.models import BaseAgent
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
def test_error_handling():
|
|
||||||
print('🧪 Testing error handling patterns...')
|
|
||||||
|
|
||||||
processor = FiveWhysAnalyzerProcessor()
|
|
||||||
user = User.objects.filter(is_superuser=True).first()
|
|
||||||
|
|
||||||
# Test 1: Missing session_id
|
|
||||||
try:
|
|
||||||
result = processor.handle_chat_message(
|
|
||||||
user=user,
|
|
||||||
message='Test message'
|
|
||||||
# No session_id - should auto-generate
|
|
||||||
)
|
|
||||||
print('✅ Missing session_id handled gracefully')
|
|
||||||
except Exception as e:
|
|
||||||
print(f'❌ Missing session_id caused error: {e}')
|
|
||||||
|
|
||||||
# Test 2: Missing user
|
|
||||||
try:
|
|
||||||
result = processor.handle_chat_message(
|
|
||||||
session_id=str(uuid.uuid4()),
|
|
||||||
message='Test message'
|
|
||||||
# No user - should raise clear error
|
|
||||||
)
|
|
||||||
print('❌ Missing user should have raised error')
|
|
||||||
except Exception as e:
|
|
||||||
print(f'✅ Missing user properly handled: {type(e).__name__}')
|
|
||||||
|
|
||||||
# Test 3: Invalid message type
|
|
||||||
try:
|
|
||||||
result = processor.process_request(
|
|
||||||
user=user,
|
|
||||||
message_type='invalid_type'
|
|
||||||
)
|
|
||||||
print('❌ Invalid message type should have raised error')
|
|
||||||
except ValueError as e:
|
|
||||||
print(f'✅ Invalid message type properly handled: {e}')
|
|
||||||
except Exception as e:
|
|
||||||
print(f'❌ Unexpected error type: {e}')
|
|
||||||
|
|
||||||
print('🎯 Error handling testing completed')
|
|
||||||
|
|
||||||
test_error_handling()
|
|
||||||
"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Performance and Index Testing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Test database performance and indexes (5 Whys optimization patterns)
|
|
||||||
python manage.py shell -c "
|
|
||||||
from django.db import connection
|
|
||||||
from five_whys_analyzer.models import FiveWhysAnalyzerRequest
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
import uuid
|
|
||||||
import time
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
def test_performance():
|
|
||||||
print('🧪 Testing database performance...')
|
|
||||||
|
|
||||||
user = User.objects.first()
|
|
||||||
if not user:
|
|
||||||
print('❌ No user found for testing')
|
|
||||||
return
|
|
||||||
|
|
||||||
# Test 1: Session lookup performance
|
|
||||||
session_id = str(uuid.uuid4())
|
|
||||||
|
|
||||||
start_time = time.time()
|
|
||||||
try:
|
|
||||||
request = FiveWhysAnalyzerRequest.objects.filter(
|
|
||||||
user=user,
|
|
||||||
session_id=session_id,
|
|
||||||
chat_active=True
|
|
||||||
).first()
|
|
||||||
end_time = time.time()
|
|
||||||
print(f'✅ Session lookup completed in {(end_time - start_time)*1000:.2f}ms')
|
|
||||||
except Exception as e:
|
|
||||||
print(f'❌ Session lookup failed: {e}')
|
|
||||||
|
|
||||||
# Test 2: Index usage check
|
|
||||||
with connection.cursor() as cursor:
|
|
||||||
cursor.execute('EXPLAIN QUERY PLAN SELECT * FROM five_whys_analyzer_requests WHERE session_id = ?', [session_id])
|
|
||||||
plan = cursor.fetchall()
|
|
||||||
|
|
||||||
# Check if index is being used
|
|
||||||
plan_text = str(plan).lower()
|
|
||||||
if 'index' in plan_text:
|
|
||||||
print('✅ Database index being used for session_id queries')
|
|
||||||
else:
|
|
||||||
print('⚠️ No index detected for session_id queries')
|
|
||||||
|
|
||||||
print('🎯 Performance testing completed')
|
|
||||||
|
|
||||||
test_performance()
|
|
||||||
"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Agent Integration Testing Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Complete agent validation script
|
|
||||||
python manage.py shell -c "
|
|
||||||
def run_complete_agent_test(agent_slug):
|
|
||||||
print(f'🚀 Running complete agent test for {agent_slug}')
|
|
||||||
print('='*50)
|
|
||||||
|
|
||||||
tests = [
|
|
||||||
('Environment', lambda: validate_agent_environment(agent_slug)),
|
|
||||||
('Lifecycle', lambda: test_agent_lifecycle(agent_slug)),
|
|
||||||
('Wallet', lambda: test_wallet_integration()),
|
|
||||||
('Templates & URLs', lambda: test_template_and_urls()),
|
|
||||||
('Error Handling', lambda: test_error_handling()),
|
|
||||||
('Performance', lambda: test_performance()),
|
|
||||||
]
|
|
||||||
|
|
||||||
results = []
|
|
||||||
for test_name, test_func in tests:
|
|
||||||
print(f'\\n🧪 Running {test_name} test...')
|
|
||||||
try:
|
|
||||||
result = test_func()
|
|
||||||
results.append((test_name, result))
|
|
||||||
if result:
|
|
||||||
print(f'✅ {test_name} test PASSED')
|
|
||||||
else:
|
|
||||||
print(f'❌ {test_name} test FAILED')
|
|
||||||
except Exception as e:
|
|
||||||
print(f'❌ {test_name} test ERROR: {e}')
|
|
||||||
results.append((test_name, False))
|
|
||||||
|
|
||||||
print(f'\\n🎯 Test Summary for {agent_slug}:')
|
|
||||||
print('='*30)
|
|
||||||
passed = sum(1 for _, result in results if result)
|
|
||||||
total = len(results)
|
|
||||||
|
|
||||||
for test_name, result in results:
|
|
||||||
status = '✅ PASS' if result else '❌ FAIL'
|
|
||||||
print(f'{test_name}: {status}')
|
|
||||||
|
|
||||||
print(f'\\nOverall: {passed}/{total} tests passed')
|
|
||||||
if passed == total:
|
|
||||||
print('🎉 All tests passed! Agent is ready for production.')
|
|
||||||
else:
|
|
||||||
print('⚠️ Some tests failed. Please review and fix issues.')
|
|
||||||
|
|
||||||
# Run for 5 Whys Analyzer
|
|
||||||
run_complete_agent_test('five-whys-analyzer')
|
|
||||||
"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5 Whys Debugging Workflow
|
|
||||||
|
|
||||||
When issues arise during agent development, follow this debugging workflow learned from 5 Whys experience:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Basic validation
|
|
||||||
python manage.py check
|
|
||||||
python manage.py showmigrations [agent_name]
|
|
||||||
|
|
||||||
# 2. Template validation
|
|
||||||
python manage.py shell -c "
|
|
||||||
from django.template.loader import get_template
|
|
||||||
template = get_template('[agent_name]/detail.html')
|
|
||||||
print('Template found:', template.origin.name)
|
|
||||||
"
|
|
||||||
|
|
||||||
# 3. URL validation
|
|
||||||
python manage.py shell -c "
|
|
||||||
from django.urls import reverse
|
|
||||||
url = reverse('core:agent_detail', args=['[agent-slug]'])
|
|
||||||
print('URL resolved:', url)
|
|
||||||
"
|
|
||||||
|
|
||||||
# 4. Model validation
|
|
||||||
python manage.py shell -c "
|
|
||||||
from [agent_name].models import *
|
|
||||||
from agent_base.models import BaseAgent
|
|
||||||
agent = BaseAgent.objects.get(slug='[agent-slug]')
|
|
||||||
print('Agent found:', agent.name)
|
|
||||||
"
|
|
||||||
|
|
||||||
# 5. Processor validation
|
|
||||||
python manage.py shell -c "
|
|
||||||
from [agent_name].processor import [AgentName]Processor
|
|
||||||
processor = [AgentName]Processor()
|
|
||||||
print('Processor initialized successfully')
|
|
||||||
"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Production Readiness Checklist
|
|
||||||
|
|
||||||
Based on 5 Whys success patterns, verify these before deploying:
|
|
||||||
|
|
||||||
- [ ] **Template Loading**: Templates load without server restart
|
|
||||||
- [ ] **URL Routing**: All URLs resolve correctly with namespaces
|
|
||||||
- [ ] **Database**: Migrations applied, indexes created
|
|
||||||
- [ ] **Wallet Integration**: Delayed deduction pattern implemented
|
|
||||||
- [ ] **Error Handling**: Comprehensive try-catch blocks
|
|
||||||
- [ ] **Session Management**: UUID-based sessions (if applicable)
|
|
||||||
- [ ] **Status Tracking**: Request lifecycle properly managed
|
|
||||||
- [ ] **Environment Variables**: All required variables validated
|
|
||||||
- [ ] **Performance**: Database queries optimized with indexes
|
|
||||||
- [ ] **Testing**: Complete test suite passes
|
|
||||||
|
|
||||||
**🎯 Following these testing procedures ensures the same level of reliability achieved with the 5 Whys Analyzer.**
|
|
||||||
|
|
||||||
Happy coding! 🎉
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,634 +0,0 @@
|
|||||||
# Documentation and Knowledge Management System
|
|
||||||
|
|
||||||
A comprehensive system for capturing, organizing, and leveraging implementation knowledge to ensure consistent quality and prevent repeated failures.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This system provides structured approaches to document implementations, capture lessons learned, and build institutional knowledge that prevents the recurrence of issues like those experienced with the Social Ads Generator initial implementation.
|
|
||||||
|
|
||||||
## Knowledge Capture Framework
|
|
||||||
|
|
||||||
### 1. Implementation Documentation Standard
|
|
||||||
|
|
||||||
**Purpose**: Ensure every implementation is thoroughly documented for future reference and learning.
|
|
||||||
|
|
||||||
**Documentation Template:**
|
|
||||||
```markdown
|
|
||||||
# Implementation Documentation: [Agent Name] - [Date]
|
|
||||||
|
|
||||||
## Implementation Summary
|
|
||||||
- **Agent**: [Agent Name]
|
|
||||||
- **Template**: [Template Path]
|
|
||||||
- **Implementer**: [Name]
|
|
||||||
- **Start Date**: [Date]
|
|
||||||
- **Completion Date**: [Date]
|
|
||||||
- **Total Duration**: [Hours/Days]
|
|
||||||
- **Complexity Level**: [Low/Medium/High]
|
|
||||||
|
|
||||||
## Requirements Analysis
|
|
||||||
### Original Request
|
|
||||||
**User Request**: [Exact quote from user]
|
|
||||||
**Clarifications**: [Any clarifications received]
|
|
||||||
|
|
||||||
### Explicit Requirements
|
|
||||||
1. [Requirement 1]
|
|
||||||
2. [Requirement 2]
|
|
||||||
3. [Requirement 3]
|
|
||||||
|
|
||||||
### Implicit Requirements
|
|
||||||
1. [Implied requirement 1] - [Reasoning]
|
|
||||||
2. [Implied requirement 2] - [Reasoning]
|
|
||||||
|
|
||||||
### Success Criteria
|
|
||||||
- **Visual**: [What should it look like?]
|
|
||||||
- **Functional**: [How should it behave?]
|
|
||||||
- **Technical**: [What technical standards?]
|
|
||||||
|
|
||||||
## Implementation Approach
|
|
||||||
### Strategy Selected
|
|
||||||
- **Approach**: [Comprehensive rewrite / Incremental updates / Hybrid]
|
|
||||||
- **Reasoning**: [Why this approach was chosen]
|
|
||||||
- **Risk Assessment**: [Risk level and mitigation strategies]
|
|
||||||
|
|
||||||
### Implementation Steps
|
|
||||||
1. **Phase 1**: [Description and outcomes]
|
|
||||||
2. **Phase 2**: [Description and outcomes]
|
|
||||||
3. **Phase 3**: [Description and outcomes]
|
|
||||||
|
|
||||||
### Changes Made
|
|
||||||
#### HTML Structure Changes
|
|
||||||
- [Change 1]: [Description and reasoning]
|
|
||||||
- [Change 2]: [Description and reasoning]
|
|
||||||
|
|
||||||
#### CSS Architecture Changes
|
|
||||||
- [Change 1]: [Description and reasoning]
|
|
||||||
- [Change 2]: [Description and reasoning]
|
|
||||||
|
|
||||||
#### JavaScript Function Changes
|
|
||||||
- [Change 1]: [Description and reasoning]
|
|
||||||
- [Change 2]: [Description and reasoning]
|
|
||||||
|
|
||||||
## Challenges Encountered
|
|
||||||
### Challenge 1: [Challenge Name]
|
|
||||||
- **Description**: [What was the challenge?]
|
|
||||||
- **Impact**: [How did it affect the implementation?]
|
|
||||||
- **Resolution**: [How was it resolved?]
|
|
||||||
- **Time Lost**: [Hours/days lost]
|
|
||||||
- **Prevention**: [How to prevent in future]
|
|
||||||
|
|
||||||
### Challenge 2: [Challenge Name]
|
|
||||||
- **Description**: [What was the challenge?]
|
|
||||||
- **Impact**: [How did it affect the implementation?]
|
|
||||||
- **Resolution**: [How was it resolved?]
|
|
||||||
- **Time Lost**: [Hours/days lost]
|
|
||||||
- **Prevention**: [How to prevent in future]
|
|
||||||
|
|
||||||
## Lessons Learned
|
|
||||||
### What Worked Well
|
|
||||||
1. [Success factor 1] - [Why it worked]
|
|
||||||
2. [Success factor 2] - [Why it worked]
|
|
||||||
3. [Success factor 3] - [Why it worked]
|
|
||||||
|
|
||||||
### What Could Be Improved
|
|
||||||
1. [Improvement area 1] - [Specific improvement]
|
|
||||||
2. [Improvement area 2] - [Specific improvement]
|
|
||||||
3. [Improvement area 3] - [Specific improvement]
|
|
||||||
|
|
||||||
### Key Insights
|
|
||||||
1. [Insight 1] - [Application for future]
|
|
||||||
2. [Insight 2] - [Application for future]
|
|
||||||
3. [Insight 3] - [Application for future]
|
|
||||||
|
|
||||||
## Quality Metrics
|
|
||||||
### Performance Metrics
|
|
||||||
- **Implementation Time**: [Actual vs. Estimated]
|
|
||||||
- **Error Rate**: [Number of issues encountered]
|
|
||||||
- **Rework Rate**: [Percentage of work redone]
|
|
||||||
- **User Satisfaction**: [Rating/feedback]
|
|
||||||
|
|
||||||
### Quality Metrics
|
|
||||||
- **Code Quality Score**: [Assessment rating]
|
|
||||||
- **Test Coverage**: [Percentage]
|
|
||||||
- **Accessibility Compliance**: [Pass/Fail/Partial]
|
|
||||||
- **Performance Score**: [Lighthouse/measurement score]
|
|
||||||
|
|
||||||
## Future Recommendations
|
|
||||||
### For Similar Implementations
|
|
||||||
1. [Recommendation 1] - [Specific guidance]
|
|
||||||
2. [Recommendation 2] - [Specific guidance]
|
|
||||||
3. [Recommendation 3] - [Specific guidance]
|
|
||||||
|
|
||||||
### For Process Improvement
|
|
||||||
1. [Process improvement 1] - [Implementation]
|
|
||||||
2. [Process improvement 2] - [Implementation]
|
|
||||||
3. [Process improvement 3] - [Implementation]
|
|
||||||
|
|
||||||
## Artifacts and References
|
|
||||||
### Code Artifacts
|
|
||||||
- **Source Template**: [Path/URL]
|
|
||||||
- **Final Implementation**: [Path/URL]
|
|
||||||
- **Backup/Archive**: [Path/URL]
|
|
||||||
|
|
||||||
### Documentation Artifacts
|
|
||||||
- **Requirements Analysis**: [Path/URL]
|
|
||||||
- **Implementation Plan**: [Path/URL]
|
|
||||||
- **Test Results**: [Path/URL]
|
|
||||||
- **User Feedback**: [Path/URL]
|
|
||||||
|
|
||||||
### Reference Materials
|
|
||||||
- **Design Patterns Used**: [List]
|
|
||||||
- **External Resources**: [URLs/references]
|
|
||||||
- **Tools Used**: [List with versions]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Failure Analysis Framework
|
|
||||||
|
|
||||||
**Purpose**: Systematically analyze failures to prevent recurrence.
|
|
||||||
|
|
||||||
**Failure Analysis Template:**
|
|
||||||
```markdown
|
|
||||||
# Failure Analysis: [Incident Name] - [Date]
|
|
||||||
|
|
||||||
## Incident Summary
|
|
||||||
- **Date/Time**: [When it occurred]
|
|
||||||
- **Severity**: [Critical/High/Medium/Low]
|
|
||||||
- **Impact**: [User impact description]
|
|
||||||
- **Duration**: [How long the issue persisted]
|
|
||||||
- **Detection Method**: [How was it discovered]
|
|
||||||
|
|
||||||
## Root Cause Analysis
|
|
||||||
### Immediate Cause
|
|
||||||
**What directly caused the failure?**
|
|
||||||
[Detailed description of the immediate cause]
|
|
||||||
|
|
||||||
### Contributing Factors
|
|
||||||
1. **Factor 1**: [Description and contribution level]
|
|
||||||
2. **Factor 2**: [Description and contribution level]
|
|
||||||
3. **Factor 3**: [Description and contribution level]
|
|
||||||
|
|
||||||
### Root Cause
|
|
||||||
**Why did the immediate cause occur?**
|
|
||||||
[Analysis of underlying root cause]
|
|
||||||
|
|
||||||
## Timeline of Events
|
|
||||||
| Time | Event | Action Taken | Outcome |
|
|
||||||
|------|-------|--------------|---------|
|
|
||||||
| [Time] | [Event description] | [Action] | [Result] |
|
|
||||||
| [Time] | [Event description] | [Action] | [Result] |
|
|
||||||
|
|
||||||
## Impact Assessment
|
|
||||||
### User Impact
|
|
||||||
- **Users Affected**: [Number/percentage]
|
|
||||||
- **Functionality Lost**: [Description]
|
|
||||||
- **Business Impact**: [Revenue/reputation impact]
|
|
||||||
- **User Experience**: [How users were affected]
|
|
||||||
|
|
||||||
### System Impact
|
|
||||||
- **Performance Degradation**: [Metrics]
|
|
||||||
- **Resource Usage**: [CPU/memory/network]
|
|
||||||
- **Dependent Systems**: [Other systems affected]
|
|
||||||
- **Data Integrity**: [Any data issues]
|
|
||||||
|
|
||||||
## Resolution Actions
|
|
||||||
### Immediate Actions
|
|
||||||
1. **Action 1**: [Description and effectiveness]
|
|
||||||
2. **Action 2**: [Description and effectiveness]
|
|
||||||
|
|
||||||
### Long-term Fixes
|
|
||||||
1. **Fix 1**: [Description and implementation timeline]
|
|
||||||
2. **Fix 2**: [Description and implementation timeline]
|
|
||||||
|
|
||||||
## Prevention Measures
|
|
||||||
### Process Improvements
|
|
||||||
1. **Improvement 1**: [Specific process change]
|
|
||||||
2. **Improvement 2**: [Specific process change]
|
|
||||||
|
|
||||||
### Technical Improvements
|
|
||||||
1. **Improvement 1**: [Technical enhancement]
|
|
||||||
2. **Improvement 2**: [Technical enhancement]
|
|
||||||
|
|
||||||
### Training/Knowledge
|
|
||||||
1. **Training Need 1**: [Specific training required]
|
|
||||||
2. **Training Need 2**: [Specific training required]
|
|
||||||
|
|
||||||
## Lessons Learned
|
|
||||||
### Key Takeaways
|
|
||||||
1. [Lesson 1] - [Application]
|
|
||||||
2. [Lesson 2] - [Application]
|
|
||||||
3. [Lesson 3] - [Application]
|
|
||||||
|
|
||||||
### Best Practices Identified
|
|
||||||
1. [Best practice 1] - [Implementation guidance]
|
|
||||||
2. [Best practice 2] - [Implementation guidance]
|
|
||||||
|
|
||||||
### Warning Signs
|
|
||||||
1. [Warning sign 1] - [How to detect early]
|
|
||||||
2. [Warning sign 2] - [How to detect early]
|
|
||||||
|
|
||||||
## Action Items
|
|
||||||
| Action | Owner | Due Date | Status |
|
|
||||||
|--------|-------|----------|--------|
|
|
||||||
| [Action 1] | [Name] | [Date] | [Status] |
|
|
||||||
| [Action 2] | [Name] | [Date] | [Status] |
|
|
||||||
|
|
||||||
## Follow-up
|
|
||||||
### Monitoring Plan
|
|
||||||
- **Metrics to Track**: [List of metrics]
|
|
||||||
- **Monitoring Frequency**: [How often to check]
|
|
||||||
- **Alert Thresholds**: [When to be notified]
|
|
||||||
|
|
||||||
### Review Schedule
|
|
||||||
- **1 Week Review**: [Date and focus]
|
|
||||||
- **1 Month Review**: [Date and focus]
|
|
||||||
- **3 Month Review**: [Date and focus]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Knowledge Repository Structure
|
|
||||||
|
|
||||||
### 3. Organized Knowledge Base
|
|
||||||
|
|
||||||
**Repository Structure:**
|
|
||||||
```
|
|
||||||
knowledge_base/
|
|
||||||
├── implementations/
|
|
||||||
│ ├── successful/
|
|
||||||
│ │ ├── [agent_name]_[date].md
|
|
||||||
│ │ └── ...
|
|
||||||
│ ├── failed/
|
|
||||||
│ │ ├── [incident_name]_[date].md
|
|
||||||
│ │ └── ...
|
|
||||||
│ └── templates/
|
|
||||||
│ ├── implementation_template.md
|
|
||||||
│ └── failure_analysis_template.md
|
|
||||||
├── patterns/
|
|
||||||
│ ├── design_patterns/
|
|
||||||
│ │ ├── widget_patterns.md
|
|
||||||
│ │ ├── layout_patterns.md
|
|
||||||
│ │ └── interaction_patterns.md
|
|
||||||
│ ├── code_patterns/
|
|
||||||
│ │ ├── html_patterns.md
|
|
||||||
│ │ ├── css_patterns.md
|
|
||||||
│ │ └── javascript_patterns.md
|
|
||||||
│ └── anti_patterns/
|
|
||||||
│ ├── common_mistakes.md
|
|
||||||
│ └── performance_pitfalls.md
|
|
||||||
├── best_practices/
|
|
||||||
│ ├── implementation_guidelines.md
|
|
||||||
│ ├── quality_standards.md
|
|
||||||
│ ├── testing_practices.md
|
|
||||||
│ └── security_practices.md
|
|
||||||
├── lessons_learned/
|
|
||||||
│ ├── quarterly_reviews/
|
|
||||||
│ │ ├── Q1_2024_lessons.md
|
|
||||||
│ │ └── ...
|
|
||||||
│ ├── common_issues/
|
|
||||||
│ │ ├── css_issues.md
|
|
||||||
│ │ ├── javascript_issues.md
|
|
||||||
│ │ └── responsive_issues.md
|
|
||||||
│ └── success_stories/
|
|
||||||
│ ├── optimization_wins.md
|
|
||||||
│ └── innovation_examples.md
|
|
||||||
└── metrics/
|
|
||||||
├── performance_benchmarks.md
|
|
||||||
├── quality_metrics.md
|
|
||||||
└── trend_analysis.md
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Pattern Library
|
|
||||||
|
|
||||||
**Design Pattern Documentation:**
|
|
||||||
```markdown
|
|
||||||
# Pattern: [Pattern Name]
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
**Purpose**: [What problem does this pattern solve?]
|
|
||||||
**Use Case**: [When should this pattern be used?]
|
|
||||||
**Complexity**: [Low/Medium/High]
|
|
||||||
|
|
||||||
## Implementation
|
|
||||||
### HTML Structure
|
|
||||||
```html
|
|
||||||
<!-- Pattern HTML structure -->
|
|
||||||
<div class="pattern-container">
|
|
||||||
<div class="pattern-header">
|
|
||||||
<h3 class="pattern-title">Title</h3>
|
|
||||||
<div class="pattern-controls">
|
|
||||||
<!-- Control elements -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="pattern-content">
|
|
||||||
<!-- Content area -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### CSS Styling
|
|
||||||
```css
|
|
||||||
/* Pattern CSS styles */
|
|
||||||
.pattern-container {
|
|
||||||
background: var(--surface);
|
|
||||||
border: 1px solid var(--outline);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.pattern-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: var(--spacing-md);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### JavaScript Functionality
|
|
||||||
```javascript
|
|
||||||
// Pattern JavaScript behavior
|
|
||||||
class PatternComponent {
|
|
||||||
constructor(element) {
|
|
||||||
this.element = element;
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {
|
|
||||||
this.setupEventListeners();
|
|
||||||
this.setupAccessibility();
|
|
||||||
}
|
|
||||||
|
|
||||||
setupEventListeners() {
|
|
||||||
// Event listener setup
|
|
||||||
}
|
|
||||||
|
|
||||||
setupAccessibility() {
|
|
||||||
// Accessibility enhancements
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Variations
|
|
||||||
### Variation 1: [Name]
|
|
||||||
**Difference**: [How it differs from base pattern]
|
|
||||||
**Use Case**: [When to use this variation]
|
|
||||||
**Implementation**: [Specific implementation details]
|
|
||||||
|
|
||||||
## Accessibility
|
|
||||||
- **ARIA Attributes**: [Required ARIA attributes]
|
|
||||||
- **Keyboard Navigation**: [Keyboard interaction support]
|
|
||||||
- **Screen Reader**: [Screen reader considerations]
|
|
||||||
- **Color Contrast**: [Color contrast requirements]
|
|
||||||
|
|
||||||
## Browser Support
|
|
||||||
- **Supported Browsers**: [List of supported browsers]
|
|
||||||
- **Fallbacks**: [Fallback implementations]
|
|
||||||
- **Progressive Enhancement**: [Enhancement strategy]
|
|
||||||
|
|
||||||
## Performance
|
|
||||||
- **Performance Impact**: [Performance considerations]
|
|
||||||
- **Optimization Tips**: [How to optimize]
|
|
||||||
- **Memory Usage**: [Memory considerations]
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
### Example 1: [Example Name]
|
|
||||||
**Context**: [Where this example is used]
|
|
||||||
**Implementation**: [Link to live example]
|
|
||||||
**Code**: [Link to source code]
|
|
||||||
|
|
||||||
## Related Patterns
|
|
||||||
- **[Pattern 1]**: [Relationship description]
|
|
||||||
- **[Pattern 2]**: [Relationship description]
|
|
||||||
|
|
||||||
## Version History
|
|
||||||
- **v1.0**: [Initial implementation - date]
|
|
||||||
- **v1.1**: [Changes made - date]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Knowledge Sharing System
|
|
||||||
|
|
||||||
### 5. Regular Knowledge Reviews
|
|
||||||
|
|
||||||
**Monthly Knowledge Review Process:**
|
|
||||||
```markdown
|
|
||||||
# Monthly Knowledge Review: [Month Year]
|
|
||||||
|
|
||||||
## Implementation Review
|
|
||||||
### Implementations Completed
|
|
||||||
| Agent | Complexity | Duration | Issues | Quality Score |
|
|
||||||
|-------|------------|----------|---------|---------------|
|
|
||||||
| [Name] | [Level] | [Time] | [Count] | [Score] |
|
|
||||||
|
|
||||||
### Common Issues Identified
|
|
||||||
1. **Issue 1**: [Description and frequency]
|
|
||||||
- **Root Cause**: [Analysis]
|
|
||||||
- **Prevention**: [Recommended action]
|
|
||||||
|
|
||||||
2. **Issue 2**: [Description and frequency]
|
|
||||||
- **Root Cause**: [Analysis]
|
|
||||||
- **Prevention**: [Recommended action]
|
|
||||||
|
|
||||||
### Success Patterns
|
|
||||||
1. **Pattern 1**: [Description]
|
|
||||||
- **Success Factor**: [Why it worked]
|
|
||||||
- **Replication**: [How to replicate]
|
|
||||||
|
|
||||||
## Knowledge Gaps Identified
|
|
||||||
### Documentation Gaps
|
|
||||||
1. [Gap 1] - [Impact and priority]
|
|
||||||
2. [Gap 2] - [Impact and priority]
|
|
||||||
|
|
||||||
### Training Needs
|
|
||||||
1. [Need 1] - [Target audience and urgency]
|
|
||||||
2. [Need 2] - [Target audience and urgency]
|
|
||||||
|
|
||||||
### Process Improvements
|
|
||||||
1. [Improvement 1] - [Implementation plan]
|
|
||||||
2. [Improvement 2] - [Implementation plan]
|
|
||||||
|
|
||||||
## Action Items
|
|
||||||
| Action | Owner | Due Date | Priority |
|
|
||||||
|--------|-------|----------|----------|
|
|
||||||
| [Action 1] | [Name] | [Date] | [High/Med/Low] |
|
|
||||||
| [Action 2] | [Name] | [Date] | [High/Med/Low] |
|
|
||||||
|
|
||||||
## Metrics and Trends
|
|
||||||
### Quality Trends
|
|
||||||
- **Average Quality Score**: [Current vs. Previous]
|
|
||||||
- **Issue Reduction**: [Percentage improvement]
|
|
||||||
- **Implementation Speed**: [Time trends]
|
|
||||||
|
|
||||||
### Knowledge Utilization
|
|
||||||
- **Documentation Usage**: [Access statistics]
|
|
||||||
- **Pattern Adoption**: [Usage statistics]
|
|
||||||
- **Training Effectiveness**: [Assessment results]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. Knowledge Transfer Protocols
|
|
||||||
|
|
||||||
**Onboarding Knowledge Transfer:**
|
|
||||||
```markdown
|
|
||||||
# Knowledge Transfer Protocol: New Team Members
|
|
||||||
|
|
||||||
## Phase 1: Foundation Knowledge (Week 1)
|
|
||||||
### Required Reading
|
|
||||||
- [ ] Template Architecture Patterns
|
|
||||||
- [ ] Implementation Best Practices
|
|
||||||
- [ ] Quality Standards Documentation
|
|
||||||
- [ ] Security Guidelines
|
|
||||||
|
|
||||||
### Hands-on Learning
|
|
||||||
- [ ] Review 3 successful implementations
|
|
||||||
- [ ] Analyze 2 failure case studies
|
|
||||||
- [ ] Complete pattern library tutorial
|
|
||||||
- [ ] Practice with simple template modification
|
|
||||||
|
|
||||||
### Assessment
|
|
||||||
- [ ] Knowledge check quiz (80% pass rate)
|
|
||||||
- [ ] Practical exercise completion
|
|
||||||
- [ ] Pattern identification test
|
|
||||||
|
|
||||||
## Phase 2: Guided Practice (Week 2-3)
|
|
||||||
### Supervised Implementation
|
|
||||||
- [ ] Assign mentor for guidance
|
|
||||||
- [ ] Start with low-complexity template
|
|
||||||
- [ ] Follow documentation frameworks
|
|
||||||
- [ ] Regular check-ins and feedback
|
|
||||||
|
|
||||||
### Skills Development
|
|
||||||
- [ ] Advanced pattern usage
|
|
||||||
- [ ] Debugging techniques
|
|
||||||
- [ ] Performance optimization
|
|
||||||
- [ ] Testing methodologies
|
|
||||||
|
|
||||||
### Assessment
|
|
||||||
- [ ] Implementation quality review
|
|
||||||
- [ ] Peer code review
|
|
||||||
- [ ] Mentor evaluation
|
|
||||||
|
|
||||||
## Phase 3: Independent Work (Week 4+)
|
|
||||||
### Autonomous Implementation
|
|
||||||
- [ ] Medium complexity assignments
|
|
||||||
- [ ] Self-directed learning
|
|
||||||
- [ ] Knowledge contribution
|
|
||||||
- [ ] Team collaboration
|
|
||||||
|
|
||||||
### Continuous Learning
|
|
||||||
- [ ] Monthly knowledge reviews
|
|
||||||
- [ ] Pattern library updates
|
|
||||||
- [ ] Best practice sharing
|
|
||||||
- [ ] Mentoring others
|
|
||||||
```
|
|
||||||
|
|
||||||
## Continuous Improvement System
|
|
||||||
|
|
||||||
### 7. Feedback Integration Process
|
|
||||||
|
|
||||||
**Knowledge Improvement Workflow:**
|
|
||||||
```markdown
|
|
||||||
# Knowledge Improvement Workflow
|
|
||||||
|
|
||||||
## Feedback Collection
|
|
||||||
### Sources
|
|
||||||
1. **Implementation Reviews**: Post-implementation feedback
|
|
||||||
2. **User Experience**: End-user feedback and issues
|
|
||||||
3. **Team Retrospectives**: Team learning sessions
|
|
||||||
4. **Performance Data**: Metrics and analytics
|
|
||||||
5. **External Research**: Industry best practices
|
|
||||||
|
|
||||||
### Collection Methods
|
|
||||||
- [ ] Structured feedback forms
|
|
||||||
- [ ] Regular review meetings
|
|
||||||
- [ ] Issue tracking integration
|
|
||||||
- [ ] Performance monitoring
|
|
||||||
- [ ] User surveys
|
|
||||||
|
|
||||||
## Analysis and Prioritization
|
|
||||||
### Feedback Analysis
|
|
||||||
1. **Categorize Feedback**: Group by type and impact
|
|
||||||
2. **Identify Patterns**: Look for recurring themes
|
|
||||||
3. **Assess Impact**: Evaluate business and user impact
|
|
||||||
4. **Prioritize Actions**: Rank by value and effort
|
|
||||||
|
|
||||||
### Decision Framework
|
|
||||||
| Impact | Effort | Priority | Action |
|
|
||||||
|--------|--------|----------|--------|
|
|
||||||
| High | Low | P1 | Immediate implementation |
|
|
||||||
| High | Medium | P2 | Next quarter |
|
|
||||||
| High | High | P3 | Long-term planning |
|
|
||||||
| Medium | Low | P2 | Quick wins |
|
|
||||||
| Low | * | P4 | Consider for future |
|
|
||||||
|
|
||||||
## Implementation
|
|
||||||
### Knowledge Updates
|
|
||||||
1. **Documentation Updates**: Revise existing docs
|
|
||||||
2. **New Pattern Creation**: Develop new patterns
|
|
||||||
3. **Process Improvements**: Update workflows
|
|
||||||
4. **Training Updates**: Enhance training materials
|
|
||||||
|
|
||||||
### Communication
|
|
||||||
1. **Team Notifications**: Announce changes
|
|
||||||
2. **Training Sessions**: Conduct knowledge sessions
|
|
||||||
3. **Documentation**: Update knowledge base
|
|
||||||
4. **Validation**: Confirm understanding
|
|
||||||
|
|
||||||
## Validation and Monitoring
|
|
||||||
### Effectiveness Measurement
|
|
||||||
- **Usage Metrics**: Track documentation usage
|
|
||||||
- **Quality Improvements**: Monitor implementation quality
|
|
||||||
- **Time Savings**: Measure efficiency gains
|
|
||||||
- **Error Reduction**: Track issue reduction
|
|
||||||
|
|
||||||
### Continuous Monitoring
|
|
||||||
- **Monthly Reviews**: Regular assessment
|
|
||||||
- **Quarterly Analysis**: Trend analysis
|
|
||||||
- **Annual Evaluation**: Comprehensive review
|
|
||||||
- **Feedback Loop**: Continuous improvement
|
|
||||||
```
|
|
||||||
|
|
||||||
## Implementation Tools
|
|
||||||
|
|
||||||
### 8. Knowledge Management Tools
|
|
||||||
|
|
||||||
**Documentation Generation Script:**
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
# Knowledge Base Generator
|
|
||||||
# Usage: ./generate_knowledge.sh [implementation_name]
|
|
||||||
|
|
||||||
IMPL_NAME="$1"
|
|
||||||
DATE=$(date +%Y%m%d)
|
|
||||||
KNOWLEDGE_DIR="knowledge_base/implementations/successful"
|
|
||||||
TEMPLATE_DIR="knowledge_base/templates"
|
|
||||||
|
|
||||||
# Create implementation documentation
|
|
||||||
echo "Generating implementation documentation for $IMPL_NAME..."
|
|
||||||
|
|
||||||
# Copy template and customize
|
|
||||||
cp "$TEMPLATE_DIR/implementation_template.md" "$KNOWLEDGE_DIR/${IMPL_NAME}_${DATE}.md"
|
|
||||||
|
|
||||||
# Replace placeholders
|
|
||||||
sed -i "s/\[Agent Name\]/$IMPL_NAME/g" "$KNOWLEDGE_DIR/${IMPL_NAME}_${DATE}.md"
|
|
||||||
sed -i "s/\[Date\]/$(date)/g" "$KNOWLEDGE_DIR/${IMPL_NAME}_${DATE}.md"
|
|
||||||
|
|
||||||
echo "Documentation template created: $KNOWLEDGE_DIR/${IMPL_NAME}_${DATE}.md"
|
|
||||||
echo "Please fill in the implementation details."
|
|
||||||
```
|
|
||||||
|
|
||||||
**Knowledge Search Utility:**
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
# Knowledge Search Tool
|
|
||||||
# Usage: ./search_knowledge.sh [search_term]
|
|
||||||
|
|
||||||
SEARCH_TERM="$1"
|
|
||||||
KNOWLEDGE_BASE="knowledge_base"
|
|
||||||
|
|
||||||
echo "Searching knowledge base for: $SEARCH_TERM"
|
|
||||||
echo "========================================"
|
|
||||||
|
|
||||||
# Search in all markdown files
|
|
||||||
find "$KNOWLEDGE_BASE" -name "*.md" -exec grep -l "$SEARCH_TERM" {} \; | while read file; do
|
|
||||||
echo "Found in: $file"
|
|
||||||
grep -n "$SEARCH_TERM" "$file" | head -3
|
|
||||||
echo "---"
|
|
||||||
done
|
|
||||||
```
|
|
||||||
|
|
||||||
This comprehensive documentation and knowledge management system ensures that all implementation knowledge is captured, organized, and leveraged to prevent future failures and improve overall quality.
|
|
||||||
@ -1,803 +0,0 @@
|
|||||||
# Error Prevention Guide for Agent Creation
|
|
||||||
|
|
||||||
## 🎯 Based on 5 Whys Analyzer Debugging Experience
|
|
||||||
|
|
||||||
This guide documents all the common errors encountered during agent development and their proven solutions, based on extensive debugging work that led to the successful 5 Whys Analyzer implementation.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Table of Contents
|
|
||||||
|
|
||||||
1. [Template Loading Errors](#template-loading-errors)
|
|
||||||
2. [URL Routing Issues](#url-routing-issues)
|
|
||||||
3. [Database Migration Conflicts](#database-migration-conflicts)
|
|
||||||
4. [Wallet Integration Problems](#wallet-integration-problems)
|
|
||||||
5. [Session Management Issues](#session-management-issues)
|
|
||||||
6. [Status Tracking Problems](#status-tracking-problems)
|
|
||||||
7. [Error Handling Failures](#error-handling-failures)
|
|
||||||
8. [Environment Variable Issues](#environment-variable-issues)
|
|
||||||
9. [N8N Webhook Problems](#n8n-webhook-problems)
|
|
||||||
10. [Performance and Index Issues](#performance-and-index-issues)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Template Loading Errors
|
|
||||||
|
|
||||||
### ❌ Common Error
|
|
||||||
```
|
|
||||||
TemplateDoesNotExist: detail.html
|
|
||||||
django.template.loader.TemplateDoesNotExist: detail.html
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔍 Root Cause Analysis
|
|
||||||
- Template in wrong directory structure
|
|
||||||
- Django server cache holding old template paths
|
|
||||||
- Missing app in INSTALLED_APPS
|
|
||||||
- Incorrect template naming convention
|
|
||||||
|
|
||||||
### ✅ 5 Whys Learned Solution
|
|
||||||
|
|
||||||
**Correct Template Structure:**
|
|
||||||
```bash
|
|
||||||
# ✅ Correct - 5 Whys pattern
|
|
||||||
agent_five_whys_analyzer/
|
|
||||||
└── templates/
|
|
||||||
└── five_whys_analyzer/
|
|
||||||
└── detail.html
|
|
||||||
|
|
||||||
# ❌ Wrong - causes TemplateDoesNotExist
|
|
||||||
agent_five_whys_analyzer/
|
|
||||||
└── templates/
|
|
||||||
└── detail.html # Missing app subdirectory
|
|
||||||
```
|
|
||||||
|
|
||||||
**Template Path Validation Script:**
|
|
||||||
```bash
|
|
||||||
# Test template loading before starting server
|
|
||||||
python manage.py shell -c "
|
|
||||||
from django.template.loader import get_template
|
|
||||||
try:
|
|
||||||
template = get_template('five_whys_analyzer/detail.html')
|
|
||||||
print('✅ Template found:', template.origin.name)
|
|
||||||
except Exception as e:
|
|
||||||
print('❌ Template error:', e)
|
|
||||||
"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Critical Fix Steps:**
|
|
||||||
1. Create proper directory structure
|
|
||||||
2. Move template to correct location
|
|
||||||
3. **RESTART Django server** (cache issue)
|
|
||||||
4. Verify template loading with shell command
|
|
||||||
|
|
||||||
### 🛡️ Prevention Strategy
|
|
||||||
```bash
|
|
||||||
# Template creation checklist
|
|
||||||
mkdir -p [agent_name]/templates/[agent_name]/
|
|
||||||
cp existing_working_template.html [agent_name]/templates/[agent_name]/detail.html
|
|
||||||
# Always restart server after template changes
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. URL Routing Issues
|
|
||||||
|
|
||||||
### ❌ Common Errors
|
|
||||||
```
|
|
||||||
NoReverseMatch: Reverse for 'wallet' not found
|
|
||||||
django.urls.exceptions.NoReverseMatch at /agents/five-whys-analyzer/
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔍 Root Cause Analysis
|
|
||||||
- Missing URL namespaces in templates
|
|
||||||
- Incorrect URL registration order
|
|
||||||
- Agent URLs placed after catch-all core URLs
|
|
||||||
|
|
||||||
### ✅ 5 Whys Learned Solution
|
|
||||||
|
|
||||||
**Correct URL Namespacing in Templates:**
|
|
||||||
```html
|
|
||||||
<!-- ❌ Wrong - causes NoReverseMatch -->
|
|
||||||
<a href="{% url 'wallet' %}">Wallet</a>
|
|
||||||
<a href="{% url 'homepage' %}">Home</a>
|
|
||||||
|
|
||||||
<!-- ✅ Correct - 5 Whys pattern -->
|
|
||||||
<a href="{% url 'core:wallet' %}">Wallet</a>
|
|
||||||
<a href="{% url 'core:homepage' %}">Home</a>
|
|
||||||
<a href="{% url 'authentication:login' %}">Login</a>
|
|
||||||
```
|
|
||||||
|
|
||||||
**Correct URL Registration Order:**
|
|
||||||
```python
|
|
||||||
# netcop_hub/urls.py - CRITICAL ORDER
|
|
||||||
urlpatterns = [
|
|
||||||
path('admin/', admin.site.urls),
|
|
||||||
path('auth/', include('authentication.urls')),
|
|
||||||
|
|
||||||
# ✅ Agent URLs MUST come before core URLs
|
|
||||||
path('agents/weather-reporter/', include('weather_reporter.urls')),
|
|
||||||
path('agents/five-whys-analyzer/', include('five_whys_analyzer.urls')),
|
|
||||||
|
|
||||||
# ❌ Core URLs with catch-all pattern must be LAST
|
|
||||||
path('', include('core.urls')), # This catches everything - put LAST
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
**URL Testing Commands:**
|
|
||||||
```bash
|
|
||||||
# Test URL resolution
|
|
||||||
python manage.py shell -c "
|
|
||||||
from django.urls import reverse
|
|
||||||
try:
|
|
||||||
url = reverse('core:agent_detail', args=['five-whys-analyzer'])
|
|
||||||
print('✅ URL resolved:', url)
|
|
||||||
except Exception as e:
|
|
||||||
print('❌ URL error:', e)
|
|
||||||
"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🛡️ Prevention Strategy
|
|
||||||
- Always use namespaced URLs in templates
|
|
||||||
- Register agent URLs before core URLs
|
|
||||||
- Test URL resolution after each agent creation
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Database Migration Conflicts
|
|
||||||
|
|
||||||
### ❌ Common Errors
|
|
||||||
```
|
|
||||||
django.db.utils.ProgrammingError: relation "five_whys_analyzer_requests" already exists
|
|
||||||
django.db.migrations.exceptions.InconsistentMigrationHistory
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔍 Root Cause Analysis
|
|
||||||
- Django migration state out of sync with actual database
|
|
||||||
- Manually created tables conflicting with migrations
|
|
||||||
- Migration dependencies missing or circular
|
|
||||||
|
|
||||||
### ✅ 5 Whys Learned Solution
|
|
||||||
|
|
||||||
**Manual Migration Sync Fix:**
|
|
||||||
```bash
|
|
||||||
# 1. Check current migration state
|
|
||||||
python manage.py showmigrations five_whys_analyzer
|
|
||||||
|
|
||||||
# 2. Create empty migration to sync state
|
|
||||||
python manage.py makemigrations five_whys_analyzer --empty --name fix_migration_sync
|
|
||||||
|
|
||||||
# 3. Edit the migration file to match current state
|
|
||||||
# migrations/000X_fix_migration_sync.py
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
dependencies = [
|
|
||||||
('five_whys_analyzer', '0001_initial'),
|
|
||||||
]
|
|
||||||
operations = [
|
|
||||||
# Empty operations - just sync Django state
|
|
||||||
]
|
|
||||||
|
|
||||||
# 4. Apply migration
|
|
||||||
python manage.py migrate five_whys_analyzer
|
|
||||||
```
|
|
||||||
|
|
||||||
**Conflict Resolution Pattern:**
|
|
||||||
```bash
|
|
||||||
# If migration conflicts persist
|
|
||||||
python manage.py migrate five_whys_analyzer --fake-initial
|
|
||||||
python manage.py migrate five_whys_analyzer
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🛡️ Prevention Strategy
|
|
||||||
- Always run `makemigrations` immediately after model changes
|
|
||||||
- Test migrations on clean database before production
|
|
||||||
- Keep migration files in version control
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Wallet Integration Problems
|
|
||||||
|
|
||||||
### ❌ Common Errors
|
|
||||||
```
|
|
||||||
AttributeError: 'User' object has no attribute 'deduct_balance'
|
|
||||||
decimal.InvalidOperation: [<class 'decimal.ConversionSyntax'>]
|
|
||||||
Wallet balance incorrectly deducted for failed requests
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔍 Root Cause Analysis
|
|
||||||
- Deducting balance before processing completion
|
|
||||||
- Incorrect decimal handling for currency
|
|
||||||
- Missing wallet methods in User model
|
|
||||||
|
|
||||||
### ✅ 5 Whys Learned Solution
|
|
||||||
|
|
||||||
**Delayed Deduction Pattern (Critical):**
|
|
||||||
```python
|
|
||||||
# ❌ Wrong - deduct before processing
|
|
||||||
def process_view(request):
|
|
||||||
# Bad: deduct immediately
|
|
||||||
request.user.deduct_balance(agent.price, description, agent_slug)
|
|
||||||
result = process_request() # What if this fails?
|
|
||||||
return result
|
|
||||||
|
|
||||||
# ✅ Correct - 5 Whys pattern (deduct after success)
|
|
||||||
def process_report_response(self, response_data, request_obj):
|
|
||||||
try:
|
|
||||||
# Process first
|
|
||||||
final_report = response_data.get('output', '')
|
|
||||||
success = bool(final_report) and response_data.get('success', True)
|
|
||||||
|
|
||||||
if success:
|
|
||||||
# Save successful response
|
|
||||||
response_obj.final_report = final_report
|
|
||||||
response_obj.save()
|
|
||||||
|
|
||||||
# ONLY deduct after confirmed success
|
|
||||||
request_obj.user.deduct_balance(
|
|
||||||
request_obj.cost,
|
|
||||||
f"5 Whys Analysis Agent - Final Report",
|
|
||||||
'five-whys-analyzer'
|
|
||||||
)
|
|
||||||
request_obj.status = 'completed'
|
|
||||||
else:
|
|
||||||
request_obj.status = 'failed'
|
|
||||||
# No wallet deduction for failures
|
|
||||||
|
|
||||||
request_obj.save()
|
|
||||||
return response_obj
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
request_obj.status = 'failed'
|
|
||||||
request_obj.save()
|
|
||||||
# No wallet deduction for exceptions
|
|
||||||
raise Exception(f"Failed to process: {e}")
|
|
||||||
```
|
|
||||||
|
|
||||||
**Decimal Handling:**
|
|
||||||
```python
|
|
||||||
# ✅ Correct decimal usage
|
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
# Always use Decimal for currency
|
|
||||||
agent.price = Decimal('8.00')
|
|
||||||
request_obj.cost = Decimal('8.00')
|
|
||||||
|
|
||||||
# Check balance properly
|
|
||||||
if request.user.wallet_balance >= agent.price:
|
|
||||||
# Proceed
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🛡️ Prevention Strategy
|
|
||||||
- Never deduct balance before processing completion
|
|
||||||
- Always use Decimal for currency calculations
|
|
||||||
- Implement balance checks before processing
|
|
||||||
- Test wallet integration with both success and failure scenarios
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Session Management Issues
|
|
||||||
|
|
||||||
### ❌ Common Errors
|
|
||||||
```
|
|
||||||
KeyError: 'session_id'
|
|
||||||
Multiple chat sessions created for same user
|
|
||||||
Session state lost between requests
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔍 Root Cause Analysis
|
|
||||||
- Missing session ID handling
|
|
||||||
- No persistent session storage
|
|
||||||
- Poor session lifecycle management
|
|
||||||
|
|
||||||
### ✅ 5 Whys Learned Solution
|
|
||||||
|
|
||||||
**Session-Based Model Pattern:**
|
|
||||||
```python
|
|
||||||
# 5 Whys session management pattern
|
|
||||||
class AgentRequest(BaseAgentRequest):
|
|
||||||
# Session management
|
|
||||||
session_id = models.CharField(max_length=100, default=uuid.uuid4, db_index=True)
|
|
||||||
|
|
||||||
# Session state tracking
|
|
||||||
chat_messages = models.JSONField(default=list)
|
|
||||||
chat_active = models.BooleanField(default=True)
|
|
||||||
report_generated = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
indexes = [
|
|
||||||
models.Index(fields=['session_id']),
|
|
||||||
models.Index(fields=['user', 'chat_active']),
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Session Retrieval Pattern:**
|
|
||||||
```python
|
|
||||||
# Safe session handling
|
|
||||||
def handle_chat_message(self, **kwargs):
|
|
||||||
user = kwargs.get('user')
|
|
||||||
session_id = kwargs.get('session_id', str(uuid.uuid4()))
|
|
||||||
|
|
||||||
# Get or create session
|
|
||||||
request_obj, created = AgentRequest.objects.get_or_create(
|
|
||||||
user=user,
|
|
||||||
session_id=session_id,
|
|
||||||
chat_active=True,
|
|
||||||
defaults={
|
|
||||||
'agent': agent,
|
|
||||||
'cost': 0, # No cost for chat
|
|
||||||
'status': 'pending'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Add message to history
|
|
||||||
chat_messages = request_obj.chat_messages
|
|
||||||
chat_messages.append({
|
|
||||||
'role': 'user',
|
|
||||||
'message': user_message,
|
|
||||||
'timestamp': timezone.now().isoformat()
|
|
||||||
})
|
|
||||||
request_obj.chat_messages = chat_messages
|
|
||||||
request_obj.save()
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🛡️ Prevention Strategy
|
|
||||||
- Always use UUID for session IDs
|
|
||||||
- Index session_id field for performance
|
|
||||||
- Implement session cleanup for old sessions
|
|
||||||
- Test session persistence across requests
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Status Tracking Problems
|
|
||||||
|
|
||||||
### ❌ Common Errors
|
|
||||||
```
|
|
||||||
Requests stuck in 'processing' status
|
|
||||||
Status not updated after completion
|
|
||||||
Inconsistent status across request lifecycle
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔍 Root Cause Analysis
|
|
||||||
- Missing status updates in error paths
|
|
||||||
- No status transitions defined
|
|
||||||
- Exception handling bypassing status updates
|
|
||||||
|
|
||||||
### ✅ 5 Whys Learned Solution
|
|
||||||
|
|
||||||
**Status Lifecycle Pattern:**
|
|
||||||
```python
|
|
||||||
# 5 Whys status tracking pattern
|
|
||||||
def process_response(self, response_data, request_obj):
|
|
||||||
try:
|
|
||||||
# Always update status to processing
|
|
||||||
request_obj.status = 'processing'
|
|
||||||
request_obj.save()
|
|
||||||
|
|
||||||
# Process the request
|
|
||||||
success = self.extract_and_validate_response(response_data)
|
|
||||||
|
|
||||||
# Update status based on result
|
|
||||||
if success:
|
|
||||||
request_obj.status = 'completed'
|
|
||||||
# Handle successful response
|
|
||||||
else:
|
|
||||||
request_obj.status = 'failed'
|
|
||||||
# Handle failed response
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# Always handle errors with status update
|
|
||||||
request_obj.status = 'failed'
|
|
||||||
request_obj.save()
|
|
||||||
raise
|
|
||||||
finally:
|
|
||||||
# Always set processed timestamp
|
|
||||||
request_obj.processed_at = timezone.now()
|
|
||||||
request_obj.save()
|
|
||||||
```
|
|
||||||
|
|
||||||
**Status Validation:**
|
|
||||||
```python
|
|
||||||
# Status transition validation
|
|
||||||
VALID_STATUS_TRANSITIONS = {
|
|
||||||
'pending': ['processing', 'failed'],
|
|
||||||
'processing': ['completed', 'failed'],
|
|
||||||
'completed': [], # Terminal state
|
|
||||||
'failed': [], # Terminal state
|
|
||||||
}
|
|
||||||
|
|
||||||
def update_status(self, request_obj, new_status):
|
|
||||||
current_status = request_obj.status
|
|
||||||
if new_status not in VALID_STATUS_TRANSITIONS.get(current_status, []):
|
|
||||||
raise ValueError(f"Invalid status transition: {current_status} -> {new_status}")
|
|
||||||
request_obj.status = new_status
|
|
||||||
request_obj.save()
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🛡️ Prevention Strategy
|
|
||||||
- Define clear status lifecycle
|
|
||||||
- Always update status in exception handlers
|
|
||||||
- Use try-finally blocks for cleanup
|
|
||||||
- Monitor requests stuck in processing status
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Error Handling Failures
|
|
||||||
|
|
||||||
### ❌ Common Errors
|
|
||||||
```
|
|
||||||
Unhandled exceptions breaking request flow
|
|
||||||
Users see raw Django error pages
|
|
||||||
No error logging for debugging
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔍 Root Cause Analysis
|
|
||||||
- Missing try-catch blocks
|
|
||||||
- No graceful error recovery
|
|
||||||
- Poor error messaging to users
|
|
||||||
|
|
||||||
### ✅ 5 Whys Learned Solution
|
|
||||||
|
|
||||||
**Comprehensive Error Handling Pattern:**
|
|
||||||
```python
|
|
||||||
# 5 Whys error handling pattern
|
|
||||||
def process_request(self, **kwargs):
|
|
||||||
request_obj = None
|
|
||||||
try:
|
|
||||||
# Create request object
|
|
||||||
request_obj = self.create_request_object(**kwargs)
|
|
||||||
|
|
||||||
# Process the request
|
|
||||||
response_data = self.make_api_call(**kwargs)
|
|
||||||
|
|
||||||
# Handle response
|
|
||||||
return self.process_response(response_data, request_obj)
|
|
||||||
|
|
||||||
except ValidationError as e:
|
|
||||||
# User input error - don't log as system error
|
|
||||||
self.handle_user_error(request_obj, f"Invalid input: {e}")
|
|
||||||
raise Exception(f"Please check your input: {e}")
|
|
||||||
|
|
||||||
except requests.RequestException as e:
|
|
||||||
# External API error - log and retry
|
|
||||||
self.log_api_error(e, request_obj)
|
|
||||||
self.handle_api_error(request_obj, "External service temporarily unavailable")
|
|
||||||
raise Exception("Service temporarily unavailable. Please try again later.")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# Unknown error - log everything for debugging
|
|
||||||
self.log_system_error(e, request_obj, **kwargs)
|
|
||||||
self.handle_system_error(request_obj, "An unexpected error occurred")
|
|
||||||
raise Exception("An unexpected error occurred. Please contact support.")
|
|
||||||
|
|
||||||
def handle_user_error(self, request_obj, message):
|
|
||||||
if request_obj:
|
|
||||||
request_obj.status = 'failed'
|
|
||||||
request_obj.save()
|
|
||||||
# Don't log user errors as system issues
|
|
||||||
|
|
||||||
def handle_api_error(self, request_obj, message):
|
|
||||||
if request_obj:
|
|
||||||
request_obj.status = 'failed'
|
|
||||||
request_obj.save()
|
|
||||||
# Log API errors for monitoring
|
|
||||||
print(f"API Error: {message}")
|
|
||||||
|
|
||||||
def handle_system_error(self, request_obj, message):
|
|
||||||
if request_obj:
|
|
||||||
request_obj.status = 'failed'
|
|
||||||
request_obj.save()
|
|
||||||
# Log system errors with full context
|
|
||||||
print(f"SYSTEM ERROR: {message}")
|
|
||||||
|
|
||||||
def log_system_error(self, error, request_obj, **kwargs):
|
|
||||||
"""Log system errors with full context for debugging"""
|
|
||||||
error_context = {
|
|
||||||
'error': str(error),
|
|
||||||
'request_id': str(request_obj.id) if request_obj else 'None',
|
|
||||||
'user_id': kwargs.get('user', {}).get('id', 'None'),
|
|
||||||
'agent_slug': self.agent_slug,
|
|
||||||
'kwargs': kwargs
|
|
||||||
}
|
|
||||||
print(f"SYSTEM ERROR CONTEXT: {error_context}")
|
|
||||||
```
|
|
||||||
|
|
||||||
**User-Friendly Error Messages:**
|
|
||||||
```python
|
|
||||||
# Map internal errors to user-friendly messages
|
|
||||||
ERROR_MESSAGES = {
|
|
||||||
'insufficient_balance': "Insufficient wallet balance. Please top up your wallet.",
|
|
||||||
'file_too_large': "File size exceeds limit. Please upload a smaller file.",
|
|
||||||
'invalid_format': "Unsupported file format. Please upload a valid file.",
|
|
||||||
'api_timeout': "Request timed out. Please try again.",
|
|
||||||
'service_unavailable': "Service temporarily unavailable. Please try again later.",
|
|
||||||
'unknown_error': "An unexpected error occurred. Please contact support."
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_user_friendly_error(self, error_code):
|
|
||||||
return ERROR_MESSAGES.get(error_code, ERROR_MESSAGES['unknown_error'])
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🛡️ Prevention Strategy
|
|
||||||
- Wrap all external calls in try-catch blocks
|
|
||||||
- Provide user-friendly error messages
|
|
||||||
- Log errors with sufficient context for debugging
|
|
||||||
- Test error scenarios during development
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Environment Variable Issues
|
|
||||||
|
|
||||||
### ❌ Common Errors
|
|
||||||
```
|
|
||||||
KeyError: 'N8N_WEBHOOK_5_WHYS'
|
|
||||||
API authentication failures
|
|
||||||
Webhook URLs not found
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔍 Root Cause Analysis
|
|
||||||
- Environment variables not loaded
|
|
||||||
- Variable name mismatches
|
|
||||||
- Missing .env file in production
|
|
||||||
|
|
||||||
### ✅ 5 Whys Learned Solution
|
|
||||||
|
|
||||||
**Environment Variable Pattern:**
|
|
||||||
```python
|
|
||||||
# Safe environment variable loading
|
|
||||||
import os
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
class AgentProcessor:
|
|
||||||
def __init__(self):
|
|
||||||
# Safe environment variable access
|
|
||||||
self.webhook_url = self.get_env_var('N8N_WEBHOOK_5_WHYS')
|
|
||||||
self.api_key = self.get_env_var('EXTERNAL_API_KEY')
|
|
||||||
|
|
||||||
def get_env_var(self, var_name, default=None):
|
|
||||||
"""Safely get environment variable with validation"""
|
|
||||||
value = os.getenv(var_name, default)
|
|
||||||
if not value and default is None:
|
|
||||||
raise Exception(f"Required environment variable '{var_name}' not found")
|
|
||||||
return value
|
|
||||||
|
|
||||||
def validate_configuration(self):
|
|
||||||
"""Validate all required environment variables"""
|
|
||||||
required_vars = [
|
|
||||||
'N8N_WEBHOOK_5_WHYS',
|
|
||||||
'DATABASE_URL',
|
|
||||||
'SECRET_KEY'
|
|
||||||
]
|
|
||||||
|
|
||||||
missing_vars = []
|
|
||||||
for var in required_vars:
|
|
||||||
if not os.getenv(var):
|
|
||||||
missing_vars.append(var)
|
|
||||||
|
|
||||||
if missing_vars:
|
|
||||||
raise Exception(f"Missing required environment variables: {missing_vars}")
|
|
||||||
```
|
|
||||||
|
|
||||||
**Environment Variable Validation Command:**
|
|
||||||
```bash
|
|
||||||
# Create validation script
|
|
||||||
python manage.py shell -c "
|
|
||||||
import os
|
|
||||||
required_vars = ['N8N_WEBHOOK_5_WHYS', 'OPENWEATHER_API_KEY', 'DATABASE_URL']
|
|
||||||
missing = [var for var in required_vars if not os.getenv(var)]
|
|
||||||
if missing:
|
|
||||||
print('❌ Missing variables:', missing)
|
|
||||||
else:
|
|
||||||
print('✅ All required variables present')
|
|
||||||
"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🛡️ Prevention Strategy
|
|
||||||
- Create environment variable validation script
|
|
||||||
- Use safe access patterns with defaults
|
|
||||||
- Document all required variables
|
|
||||||
- Test with missing variables to ensure graceful failure
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. N8N Webhook Problems
|
|
||||||
|
|
||||||
### ❌ Common Errors
|
|
||||||
```
|
|
||||||
Connection refused to N8N webhook
|
|
||||||
Webhook timeout errors
|
|
||||||
Invalid webhook response format
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔍 Root Cause Analysis
|
|
||||||
- N8N workflow not active
|
|
||||||
- Network connectivity issues
|
|
||||||
- Response format mismatches
|
|
||||||
|
|
||||||
### ✅ 5 Whys Learned Solution
|
|
||||||
|
|
||||||
**Webhook Validation Pattern:**
|
|
||||||
```python
|
|
||||||
# 5 Whys webhook handling pattern
|
|
||||||
class WebhookProcessor(StandardWebhookProcessor):
|
|
||||||
def make_request(self, payload):
|
|
||||||
"""Make webhook request with comprehensive error handling"""
|
|
||||||
try:
|
|
||||||
# Validate webhook URL
|
|
||||||
if not self.webhook_url:
|
|
||||||
raise Exception("Webhook URL not configured")
|
|
||||||
|
|
||||||
# Test connectivity first
|
|
||||||
self.test_webhook_connectivity()
|
|
||||||
|
|
||||||
# Make request with timeout
|
|
||||||
response = requests.post(
|
|
||||||
self.webhook_url,
|
|
||||||
json=payload,
|
|
||||||
timeout=30, # 30 second timeout
|
|
||||||
headers={'Content-Type': 'application/json'}
|
|
||||||
)
|
|
||||||
|
|
||||||
# Validate response
|
|
||||||
if response.status_code != 200:
|
|
||||||
raise Exception(f"Webhook returned status {response.status_code}: {response.text}")
|
|
||||||
|
|
||||||
# Validate response format
|
|
||||||
try:
|
|
||||||
response_data = response.json()
|
|
||||||
except ValueError:
|
|
||||||
raise Exception("Webhook returned invalid JSON")
|
|
||||||
|
|
||||||
return response_data
|
|
||||||
|
|
||||||
except requests.ConnectionError:
|
|
||||||
raise Exception("Cannot connect to N8N webhook. Check N8N service status.")
|
|
||||||
except requests.Timeout:
|
|
||||||
raise Exception("Webhook request timed out. Try again later.")
|
|
||||||
except Exception as e:
|
|
||||||
raise Exception(f"Webhook error: {e}")
|
|
||||||
|
|
||||||
def test_webhook_connectivity(self):
|
|
||||||
"""Test webhook connectivity before making actual request"""
|
|
||||||
try:
|
|
||||||
test_response = requests.get(
|
|
||||||
self.webhook_url.replace('/webhook/', '/ping/'),
|
|
||||||
timeout=5
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
except:
|
|
||||||
# Webhook connectivity test failed - continue anyway
|
|
||||||
return False
|
|
||||||
```
|
|
||||||
|
|
||||||
**Webhook Response Validation:**
|
|
||||||
```python
|
|
||||||
def validate_webhook_response(self, response_data):
|
|
||||||
"""Validate webhook response format"""
|
|
||||||
required_fields = ['output', 'success']
|
|
||||||
|
|
||||||
if not isinstance(response_data, dict):
|
|
||||||
raise Exception("Webhook response must be JSON object")
|
|
||||||
|
|
||||||
missing_fields = [field for field in required_fields if field not in response_data]
|
|
||||||
if missing_fields:
|
|
||||||
raise Exception(f"Webhook response missing fields: {missing_fields}")
|
|
||||||
|
|
||||||
return True
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🛡️ Prevention Strategy
|
|
||||||
- Always test webhook connectivity
|
|
||||||
- Implement proper timeout handling
|
|
||||||
- Validate webhook response format
|
|
||||||
- Have fallback mechanisms for webhook failures
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. Performance and Index Issues
|
|
||||||
|
|
||||||
### ❌ Common Errors
|
|
||||||
```
|
|
||||||
Slow database queries
|
|
||||||
Missing indexes on frequently queried fields
|
|
||||||
Session lookup timeouts
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🔍 Root Cause Analysis
|
|
||||||
- Missing database indexes
|
|
||||||
- Inefficient query patterns
|
|
||||||
- No query optimization
|
|
||||||
|
|
||||||
### ✅ 5 Whys Learned Solution
|
|
||||||
|
|
||||||
**Database Index Pattern:**
|
|
||||||
```python
|
|
||||||
# 5 Whys performance optimization
|
|
||||||
class AgentRequest(BaseAgentRequest):
|
|
||||||
session_id = models.CharField(max_length=100, default=uuid.uuid4, db_index=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
indexes = [
|
|
||||||
# Session-based queries
|
|
||||||
models.Index(fields=['session_id']),
|
|
||||||
models.Index(fields=['user', 'chat_active']),
|
|
||||||
|
|
||||||
# Status and time-based queries
|
|
||||||
models.Index(fields=['status', 'created_at']),
|
|
||||||
models.Index(fields=['user', 'status']),
|
|
||||||
|
|
||||||
# Agent-specific queries
|
|
||||||
models.Index(fields=['agent', 'created_at']),
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Query Optimization Pattern:**
|
|
||||||
```python
|
|
||||||
# Efficient query patterns
|
|
||||||
def get_user_active_session(self, user, agent_slug):
|
|
||||||
"""Optimized session lookup"""
|
|
||||||
return AgentRequest.objects.select_related('agent', 'user').filter(
|
|
||||||
user=user,
|
|
||||||
agent__slug=agent_slug,
|
|
||||||
chat_active=True
|
|
||||||
).first()
|
|
||||||
|
|
||||||
def get_recent_requests(self, user, limit=10):
|
|
||||||
"""Optimized recent requests lookup"""
|
|
||||||
return AgentRequest.objects.select_related('agent').filter(
|
|
||||||
user=user
|
|
||||||
).order_by('-created_at')[:limit]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 🛡️ Prevention Strategy
|
|
||||||
- Add indexes for all frequently queried fields
|
|
||||||
- Use select_related for foreign key queries
|
|
||||||
- Monitor slow queries in production
|
|
||||||
- Test with realistic data volumes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛡️ Overall Prevention Strategy
|
|
||||||
|
|
||||||
### Pre-Development Checklist
|
|
||||||
- [ ] Study 5 Whys Analyzer patterns before starting
|
|
||||||
- [ ] Plan session management if needed
|
|
||||||
- [ ] Design delayed wallet deduction flow
|
|
||||||
- [ ] Plan comprehensive error handling
|
|
||||||
|
|
||||||
### During Development Checklist
|
|
||||||
- [ ] Use proper template directory structure
|
|
||||||
- [ ] Always use namespaced URLs
|
|
||||||
- [ ] Implement delayed wallet deduction
|
|
||||||
- [ ] Add comprehensive error handling
|
|
||||||
- [ ] Create proper database indexes
|
|
||||||
|
|
||||||
### Post-Development Checklist
|
|
||||||
- [ ] Test all error scenarios
|
|
||||||
- [ ] Validate template loading
|
|
||||||
- [ ] Test URL routing
|
|
||||||
- [ ] Verify wallet integration
|
|
||||||
- [ ] Test session management
|
|
||||||
- [ ] Validate environment variables
|
|
||||||
|
|
||||||
### Production Deployment Checklist
|
|
||||||
- [ ] Run migration validation
|
|
||||||
- [ ] Test webhook connectivity
|
|
||||||
- [ ] Verify environment variables
|
|
||||||
- [ ] Monitor error rates
|
|
||||||
- [ ] Check performance metrics
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Key Takeaways from 5 Whys Debugging
|
|
||||||
|
|
||||||
1. **Template Organization is Critical**: Always use proper directory structure
|
|
||||||
2. **URL Namespaces Prevent Errors**: Always use namespaced URLs
|
|
||||||
3. **Delayed Wallet Deduction**: Never deduct before processing success
|
|
||||||
4. **Session Management**: Use UUID-based sessions for complex agents
|
|
||||||
5. **Status Tracking**: Implement proper lifecycle management
|
|
||||||
6. **Error Handling**: Wrap everything in try-catch blocks
|
|
||||||
7. **Environment Variables**: Validate all required variables
|
|
||||||
8. **Performance**: Add indexes for frequently queried fields
|
|
||||||
|
|
||||||
**Following these patterns from the 5 Whys success ensures error-free agent creation.**
|
|
||||||
@ -1,188 +0,0 @@
|
|||||||
# 🔐 Forgot Password Implementation Guide
|
|
||||||
|
|
||||||
## 🎯 Overview
|
|
||||||
This document describes the comprehensive forgot password system implemented for the NetCop Django project, including secure token generation, email integration, and Railway deployment.
|
|
||||||
|
|
||||||
## ✨ Features Implemented
|
|
||||||
|
|
||||||
### 🔧 Backend Components
|
|
||||||
- **PasswordResetToken Model**: Secure UUID-based tokens with 1-hour expiration
|
|
||||||
- **Email Integration**: Gmail SMTP configuration for production
|
|
||||||
- **Security Features**: Single-use tokens, no email enumeration protection
|
|
||||||
- **Error Handling**: Detailed logging and user-friendly error messages
|
|
||||||
|
|
||||||
### 🎨 Frontend Components
|
|
||||||
- **Professional UI**: Consistent design matching existing authentication pages
|
|
||||||
- **Responsive Design**: Mobile-friendly forms and layouts
|
|
||||||
- **User Experience**: Clear error messages and helpful navigation
|
|
||||||
- **Loading States**: Progress indicators during form submission
|
|
||||||
|
|
||||||
### 🚀 Railway Deployment
|
|
||||||
- **Environment Variables**: Proper email configuration for production
|
|
||||||
- **Database Integration**: PostgreSQL compatibility
|
|
||||||
- **SSL/HTTPS**: Secure password reset links
|
|
||||||
- **Production URLs**: Correct site URL configuration
|
|
||||||
|
|
||||||
## 📋 Implementation Details
|
|
||||||
|
|
||||||
### Database Schema
|
|
||||||
```python
|
|
||||||
class PasswordResetToken(models.Model):
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='password_reset_tokens')
|
|
||||||
token = models.UUIDField(default=uuid.uuid4, unique=True, editable=False)
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
expires_at = models.DateTimeField()
|
|
||||||
is_used = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
return not self.is_used and timezone.now() < self.expires_at
|
|
||||||
```
|
|
||||||
|
|
||||||
### URL Configuration
|
|
||||||
```python
|
|
||||||
urlpatterns = [
|
|
||||||
path('forgot-password/', views.forgot_password_view, name='forgot_password'),
|
|
||||||
path('reset-password/<uuid:token>/', views.reset_password_view, name='reset_password'),
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Email Configuration
|
|
||||||
```python
|
|
||||||
# Production settings (Railway)
|
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
|
||||||
EMAIL_HOST = 'smtp.gmail.com'
|
|
||||||
EMAIL_PORT = 587
|
|
||||||
EMAIL_USE_TLS = True
|
|
||||||
EMAIL_HOST_USER = 'your-email@gmail.com'
|
|
||||||
EMAIL_HOST_PASSWORD = 'your-app-password'
|
|
||||||
DEFAULT_FROM_EMAIL = 'NetCop <your-email@gmail.com>'
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🔒 Security Features
|
|
||||||
|
|
||||||
### Token Security
|
|
||||||
- **UUID4 Generation**: Cryptographically secure random tokens
|
|
||||||
- **1-Hour Expiration**: Automatic token invalidation
|
|
||||||
- **Single-Use**: Tokens marked as used after password reset
|
|
||||||
- **Database Storage**: Secure token storage with user association
|
|
||||||
|
|
||||||
### Email Security
|
|
||||||
- **No Email Enumeration**: Helpful error messages without revealing account existence
|
|
||||||
- **HTTPS Links**: Secure password reset URLs
|
|
||||||
- **App Passwords**: Gmail app-specific passwords for authentication
|
|
||||||
|
|
||||||
## 🎯 User Experience Flow
|
|
||||||
|
|
||||||
### 1. Request Password Reset
|
|
||||||
1. User clicks "Forgot your password?" on login page
|
|
||||||
2. Enters email address in professional form
|
|
||||||
3. Receives clear feedback (success or error message)
|
|
||||||
4. Gets helpful navigation to registration if needed
|
|
||||||
|
|
||||||
### 2. Email Delivery
|
|
||||||
1. Secure token generated and stored
|
|
||||||
2. Professional email sent with reset instructions
|
|
||||||
3. Email contains HTTPS link with embedded token
|
|
||||||
4. Link expires automatically after 1 hour
|
|
||||||
|
|
||||||
### 3. Password Reset
|
|
||||||
1. User clicks link in email
|
|
||||||
2. Redirected to secure password reset form
|
|
||||||
3. Enters new password with validation
|
|
||||||
4. Token marked as used, password updated
|
|
||||||
5. Redirected to login with success message
|
|
||||||
|
|
||||||
## 🛠️ Railway Deployment Configuration
|
|
||||||
|
|
||||||
### Environment Variables Required
|
|
||||||
```bash
|
|
||||||
EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
|
|
||||||
EMAIL_HOST=smtp.gmail.com
|
|
||||||
EMAIL_PORT=587
|
|
||||||
EMAIL_USE_TLS=True
|
|
||||||
EMAIL_HOST_USER=your-email@gmail.com
|
|
||||||
EMAIL_HOST_PASSWORD=your-app-password
|
|
||||||
DEFAULT_FROM_EMAIL=NetCop <your-email@gmail.com>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Site URL Configuration
|
|
||||||
```python
|
|
||||||
# Automatic Railway detection
|
|
||||||
if config('RAILWAY_ENVIRONMENT', default=''):
|
|
||||||
SITE_URL = 'https://netcop.up.railway.app'
|
|
||||||
else:
|
|
||||||
SITE_URL = config('SITE_URL', default='http://localhost:8000')
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🧪 Testing
|
|
||||||
|
|
||||||
### Management Command
|
|
||||||
```bash
|
|
||||||
python manage.py test_email --email=user@example.com
|
|
||||||
```
|
|
||||||
|
|
||||||
### Manual Testing Flow
|
|
||||||
1. Go to `/auth/forgot-password/`
|
|
||||||
2. Enter registered user email
|
|
||||||
3. Check email inbox (including spam folder)
|
|
||||||
4. Click reset link
|
|
||||||
5. Set new password
|
|
||||||
6. Login with new credentials
|
|
||||||
|
|
||||||
## 📁 Files Modified/Created
|
|
||||||
|
|
||||||
### Models
|
|
||||||
- `authentication/models.py` - Added PasswordResetToken model
|
|
||||||
|
|
||||||
### Views
|
|
||||||
- `authentication/views.py` - Added forgot_password_view and reset_password_view
|
|
||||||
|
|
||||||
### Templates
|
|
||||||
- `templates/authentication/forgot_password.html` - Professional forgot password form
|
|
||||||
- `templates/authentication/reset_password.html` - Password reset form
|
|
||||||
- `templates/authentication/login.html` - Added forgot password link
|
|
||||||
|
|
||||||
### URLs
|
|
||||||
- `authentication/urls.py` - Added password reset URL patterns
|
|
||||||
|
|
||||||
### Configuration
|
|
||||||
- `netcop_hub/settings.py` - Email and site URL configuration
|
|
||||||
|
|
||||||
### Management Commands
|
|
||||||
- `authentication/management/commands/test_email.py` - Email testing utility
|
|
||||||
|
|
||||||
## 🔧 Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
1. **Email not received**: Check spam folder, verify environment variables
|
|
||||||
2. **Link not working**: Ensure SITE_URL is correctly configured
|
|
||||||
3. **Token expired**: Tokens expire after 1 hour, request new reset
|
|
||||||
4. **User not found**: Register user first, then request password reset
|
|
||||||
|
|
||||||
### Debug Commands
|
|
||||||
```bash
|
|
||||||
# Test email configuration
|
|
||||||
railway run python manage.py test_email --email=user@example.com
|
|
||||||
|
|
||||||
# Check environment variables
|
|
||||||
railway run python -c "import os; print('EMAIL_HOST_USER:', os.environ.get('EMAIL_HOST_USER'))"
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎉 Success Metrics
|
|
||||||
- ✅ Professional user interface matching existing design
|
|
||||||
- ✅ Secure token-based authentication
|
|
||||||
- ✅ Production-ready email integration
|
|
||||||
- ✅ Helpful error messages and navigation
|
|
||||||
- ✅ Mobile-responsive design
|
|
||||||
- ✅ Railway deployment compatibility
|
|
||||||
- ✅ Comprehensive testing and debugging tools
|
|
||||||
|
|
||||||
## 📧 Support
|
|
||||||
For issues or questions about the forgot password system, check:
|
|
||||||
1. Railway deployment logs
|
|
||||||
2. Email configuration in environment variables
|
|
||||||
3. Database user existence
|
|
||||||
4. Gmail app password validity
|
|
||||||
|
|
||||||
---
|
|
||||||
*Implementation completed with comprehensive security, user experience, and production deployment considerations.*
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,597 +0,0 @@
|
|||||||
# Optimized Agent Creation Guide
|
|
||||||
|
|
||||||
This guide provides comprehensive patterns and best practices for creating agents without UI/UX failures, based on the optimized Data Analyzer and Job Posting Generator implementations.
|
|
||||||
|
|
||||||
## Quick Start Checklist
|
|
||||||
|
|
||||||
✅ **Template Architecture**
|
|
||||||
- [ ] Use widget-based layout with CSS custom properties
|
|
||||||
- [ ] Implement self-contained styles (no external dependencies)
|
|
||||||
- [ ] Add proper responsive design with flexbox
|
|
||||||
- [ ] Include accessibility ARIA attributes
|
|
||||||
|
|
||||||
✅ **Security Implementation**
|
|
||||||
- [ ] Implement HTML sanitization functions
|
|
||||||
- [ ] Use XSS prevention techniques
|
|
||||||
- [ ] Validate all form inputs
|
|
||||||
- [ ] Use safe DOM manipulation
|
|
||||||
|
|
||||||
✅ **Performance Optimization**
|
|
||||||
- [ ] Add debouncing for form interactions
|
|
||||||
- [ ] Implement proper event listener management
|
|
||||||
- [ ] Use efficient DOM queries
|
|
||||||
- [ ] Add loading states and feedback
|
|
||||||
|
|
||||||
✅ **Wallet Integration**
|
|
||||||
- [ ] Implement dynamic wallet balance validation
|
|
||||||
- [ ] Add wallet balance synchronization
|
|
||||||
- [ ] Include proper error handling
|
|
||||||
- [ ] Add visual feedback for balance updates
|
|
||||||
|
|
||||||
✅ **Agent Navigation**
|
|
||||||
- [ ] Implement quick agent access panel
|
|
||||||
- [ ] Add proper agent linking
|
|
||||||
- [ ] Include agent discovery features
|
|
||||||
- [ ] Add smooth transitions and animations
|
|
||||||
|
|
||||||
## Widget-Based Architecture
|
|
||||||
|
|
||||||
### Core Layout Structure
|
|
||||||
```html
|
|
||||||
<div class="agent-container">
|
|
||||||
<div class="agent-grid">
|
|
||||||
<!-- Left Column: Main Content -->
|
|
||||||
<div class="agent-main">
|
|
||||||
<div class="agent-header">...</div>
|
|
||||||
<div class="agent-form">...</div>
|
|
||||||
<div class="agent-output">...</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Right Column: Sidebar Widgets -->
|
|
||||||
<div class="agent-sidebar">
|
|
||||||
<div class="widget wallet-widget">...</div>
|
|
||||||
<div class="widget how-it-works-widget">...</div>
|
|
||||||
<div class="widget other-agents-widget">...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### CSS Custom Properties System
|
|
||||||
```css
|
|
||||||
:root {
|
|
||||||
--primary: #000000;
|
|
||||||
--surface: #ffffff;
|
|
||||||
--surface-variant: #f8fafc;
|
|
||||||
--background: #f3f4f6;
|
|
||||||
--outline: #e4e7eb;
|
|
||||||
--outline-variant: #e1e4e7;
|
|
||||||
--on-surface: #1a1a1a;
|
|
||||||
--on-surface-variant: #6b7280;
|
|
||||||
--success: #10b981;
|
|
||||||
--error: #ef4444;
|
|
||||||
--radius-sm: 8px;
|
|
||||||
--radius-md: 12px;
|
|
||||||
--radius-lg: 16px;
|
|
||||||
--spacing-xs: 4px;
|
|
||||||
--spacing-sm: 8px;
|
|
||||||
--spacing-md: 16px;
|
|
||||||
--spacing-lg: 24px;
|
|
||||||
--spacing-xl: 32px;
|
|
||||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
||||||
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
--shadow-lg: 0 10px 20px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Widget Styling Standards
|
|
||||||
```css
|
|
||||||
.widget {
|
|
||||||
background: var(--surface);
|
|
||||||
border: 1px solid var(--outline);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: var(--spacing-lg);
|
|
||||||
margin-bottom: var(--spacing-md);
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget:hover {
|
|
||||||
box-shadow: var(--shadow-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget h3 {
|
|
||||||
margin: 0 0 var(--spacing-md) 0;
|
|
||||||
font-size: 1.125rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--on-surface);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security Implementation
|
|
||||||
|
|
||||||
### HTML Sanitization Function
|
|
||||||
```javascript
|
|
||||||
function safeSetHTML(element, htmlString) {
|
|
||||||
// Create temporary container
|
|
||||||
const temp = document.createElement('div');
|
|
||||||
temp.innerHTML = htmlString;
|
|
||||||
|
|
||||||
// Remove all script tags
|
|
||||||
const scripts = temp.querySelectorAll('script');
|
|
||||||
scripts.forEach(script => script.remove());
|
|
||||||
|
|
||||||
// Remove dangerous attributes
|
|
||||||
const allElements = temp.querySelectorAll('*');
|
|
||||||
allElements.forEach(el => {
|
|
||||||
// Remove event handlers
|
|
||||||
const attrs = el.attributes;
|
|
||||||
for (let i = attrs.length - 1; i >= 0; i--) {
|
|
||||||
const attr = attrs[i];
|
|
||||||
if (attr.name.startsWith('on') ||
|
|
||||||
attr.name === 'javascript:' ||
|
|
||||||
attr.name === 'data-') {
|
|
||||||
el.removeAttribute(attr.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove dangerous href/src
|
|
||||||
if (el.tagName === 'A' && el.href && el.href.startsWith('javascript:')) {
|
|
||||||
el.removeAttribute('href');
|
|
||||||
}
|
|
||||||
if (el.tagName === 'IMG' && el.src && el.src.startsWith('javascript:')) {
|
|
||||||
el.removeAttribute('src');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set sanitized content
|
|
||||||
element.innerHTML = temp.innerHTML;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### XSS Prevention Pattern
|
|
||||||
```javascript
|
|
||||||
// Always validate and sanitize user input
|
|
||||||
function sanitizeInput(input) {
|
|
||||||
return input
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use when displaying user content
|
|
||||||
function displayUserContent(content) {
|
|
||||||
const sanitized = sanitizeInput(content);
|
|
||||||
const element = document.getElementById('output');
|
|
||||||
element.textContent = sanitized; // Use textContent, not innerHTML
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Wallet Balance Integration
|
|
||||||
|
|
||||||
### Dynamic Balance Validation
|
|
||||||
```javascript
|
|
||||||
// CRITICAL: Always read balance dynamically from DOM
|
|
||||||
function validateWalletBalance() {
|
|
||||||
const walletBalanceElement = document.getElementById('walletBalance');
|
|
||||||
const currentBalance = walletBalanceElement ?
|
|
||||||
parseFloat(walletBalanceElement.textContent) : 0;
|
|
||||||
|
|
||||||
if (currentBalance < 4.00) {
|
|
||||||
showError('Insufficient wallet balance. Please top up your wallet.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Balance Synchronization
|
|
||||||
```javascript
|
|
||||||
function updateWalletBalance(newBalance) {
|
|
||||||
if (newBalance !== undefined) {
|
|
||||||
// Update header balance
|
|
||||||
const headerBalance = document.querySelector('a[data-wallet-balance]');
|
|
||||||
if (headerBalance) {
|
|
||||||
headerBalance.textContent = `💰 ${newBalance.toFixed(2)} AED`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update page balance
|
|
||||||
const pageBalance = document.getElementById('walletBalance');
|
|
||||||
if (pageBalance) {
|
|
||||||
pageBalance.textContent = newBalance.toFixed(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Optimization
|
|
||||||
|
|
||||||
### Debounced Form Interactions
|
|
||||||
```javascript
|
|
||||||
function debounce(func, wait) {
|
|
||||||
let timeout;
|
|
||||||
return function executedFunction(...args) {
|
|
||||||
const later = () => {
|
|
||||||
clearTimeout(timeout);
|
|
||||||
func(...args);
|
|
||||||
};
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(later, wait);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Usage for form validation
|
|
||||||
const debouncedValidation = debounce(validateForm, 300);
|
|
||||||
document.getElementById('jobForm').addEventListener('input', debouncedValidation);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Event Listener Management
|
|
||||||
```javascript
|
|
||||||
class AgentManager {
|
|
||||||
constructor() {
|
|
||||||
this.eventListeners = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
addEventListeners() {
|
|
||||||
// Store references for cleanup
|
|
||||||
const submitHandler = this.handleSubmit.bind(this);
|
|
||||||
const resetHandler = this.handleReset.bind(this);
|
|
||||||
|
|
||||||
document.getElementById('submitBtn').addEventListener('click', submitHandler);
|
|
||||||
document.getElementById('resetBtn').addEventListener('click', resetHandler);
|
|
||||||
|
|
||||||
// Store for cleanup
|
|
||||||
this.eventListeners.push(
|
|
||||||
{ element: document.getElementById('submitBtn'), event: 'click', handler: submitHandler },
|
|
||||||
{ element: document.getElementById('resetBtn'), event: 'click', handler: resetHandler }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
// Remove all event listeners
|
|
||||||
this.eventListeners.forEach(({ element, event, handler }) => {
|
|
||||||
if (element) {
|
|
||||||
element.removeEventListener(event, handler);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.eventListeners = [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Accessibility Implementation
|
|
||||||
|
|
||||||
### ARIA Attributes
|
|
||||||
```html
|
|
||||||
<!-- Form with proper ARIA labeling -->
|
|
||||||
<form id="agentForm" role="main" aria-label="Agent Input Form">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="inputField" class="form-label">
|
|
||||||
Input Description
|
|
||||||
<span class="required" aria-label="required">*</span>
|
|
||||||
</label>
|
|
||||||
<textarea
|
|
||||||
id="inputField"
|
|
||||||
name="input_text"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="Enter your input here..."
|
|
||||||
aria-describedby="inputHelp"
|
|
||||||
aria-required="true"
|
|
||||||
rows="4">
|
|
||||||
</textarea>
|
|
||||||
<div id="inputHelp" class="form-text">
|
|
||||||
Provide clear, specific information for better results.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- Loading state with proper ARIA -->
|
|
||||||
<div id="loadingState" class="loading-state"
|
|
||||||
aria-live="polite"
|
|
||||||
aria-label="Processing request"
|
|
||||||
style="display: none;">
|
|
||||||
<div class="spinner" role="status" aria-hidden="true"></div>
|
|
||||||
<span class="sr-only">Processing your request...</span>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Keyboard Navigation
|
|
||||||
```javascript
|
|
||||||
// Ensure proper tab order and keyboard navigation
|
|
||||||
function enhanceAccessibility() {
|
|
||||||
// Add keyboard support for custom elements
|
|
||||||
document.querySelectorAll('.custom-button').forEach(button => {
|
|
||||||
button.setAttribute('tabindex', '0');
|
|
||||||
button.addEventListener('keydown', (e) => {
|
|
||||||
if (e.key === 'Enter' || e.key === ' ') {
|
|
||||||
e.preventDefault();
|
|
||||||
button.click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add focus management
|
|
||||||
document.addEventListener('keydown', (e) => {
|
|
||||||
if (e.key === 'Escape' && document.querySelector('.modal.active')) {
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quick Agent Access Implementation
|
|
||||||
|
|
||||||
### Right-Slide Panel
|
|
||||||
```html
|
|
||||||
<div class="widget other-agents-widget">
|
|
||||||
<h3>🤖 Explore Other Agents</h3>
|
|
||||||
<p>Discover more AI agents to boost your productivity</p>
|
|
||||||
<button class="btn btn-outline" onclick="showQuickAgentAccess()">
|
|
||||||
Quick Agent Access
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Quick Access Panel -->
|
|
||||||
<div id="quickAgentPanel" class="quick-agent-panel">
|
|
||||||
<div class="panel-header">
|
|
||||||
<h3>🚀 Quick Agent Access</h3>
|
|
||||||
<button class="panel-close" onclick="hideQuickAgentAccess()">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="panel-content">
|
|
||||||
<div class="agent-grid">
|
|
||||||
<!-- Agent cards populated dynamically -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Panel Styling
|
|
||||||
```css
|
|
||||||
.quick-agent-panel {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: -400px;
|
|
||||||
width: 400px;
|
|
||||||
height: 100vh;
|
|
||||||
background: var(--surface);
|
|
||||||
border-left: 1px solid var(--outline);
|
|
||||||
box-shadow: var(--shadow-lg);
|
|
||||||
z-index: 1000;
|
|
||||||
transition: right 0.3s ease;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-agent-panel.active {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: var(--spacing-lg);
|
|
||||||
border-bottom: 1px solid var(--outline);
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-close {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 24px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--on-surface-variant);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Panel JavaScript
|
|
||||||
```javascript
|
|
||||||
function showQuickAgentAccess() {
|
|
||||||
const panel = document.getElementById('quickAgentPanel');
|
|
||||||
const overlay = document.createElement('div');
|
|
||||||
overlay.className = 'panel-overlay';
|
|
||||||
overlay.onclick = hideQuickAgentAccess;
|
|
||||||
document.body.appendChild(overlay);
|
|
||||||
|
|
||||||
panel.classList.add('active');
|
|
||||||
document.body.style.overflow = 'hidden';
|
|
||||||
|
|
||||||
// Load agents if not already loaded
|
|
||||||
if (!panel.dataset.loaded) {
|
|
||||||
loadQuickAgents();
|
|
||||||
panel.dataset.loaded = 'true';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideQuickAgentAccess() {
|
|
||||||
const panel = document.getElementById('quickAgentPanel');
|
|
||||||
const overlay = document.querySelector('.panel-overlay');
|
|
||||||
|
|
||||||
panel.classList.remove('active');
|
|
||||||
document.body.style.overflow = '';
|
|
||||||
|
|
||||||
if (overlay) {
|
|
||||||
overlay.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadQuickAgents() {
|
|
||||||
const agents = [
|
|
||||||
{
|
|
||||||
name: 'Data Analyzer',
|
|
||||||
description: 'Analyze and interpret your data files with AI precision',
|
|
||||||
url: '/data-analyzer/',
|
|
||||||
emoji: '📊'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Job Posting Generator',
|
|
||||||
description: 'Create professional job postings in minutes',
|
|
||||||
url: '/job-posting-generator/',
|
|
||||||
emoji: '💼'
|
|
||||||
},
|
|
||||||
// Add more agents as needed
|
|
||||||
];
|
|
||||||
|
|
||||||
const container = document.querySelector('#quickAgentPanel .agent-grid');
|
|
||||||
container.innerHTML = agents.map(agent => `
|
|
||||||
<div class="agent-card">
|
|
||||||
<div class="agent-emoji">${agent.emoji}</div>
|
|
||||||
<h4>${agent.name}</h4>
|
|
||||||
<p>${agent.description}</p>
|
|
||||||
<a href="${agent.url}" class="btn btn-primary btn-sm">
|
|
||||||
Try Now
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
`).join('');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Form Validation Patterns
|
|
||||||
|
|
||||||
### Client-Side Validation
|
|
||||||
```javascript
|
|
||||||
function validateForm() {
|
|
||||||
const form = document.getElementById('agentForm');
|
|
||||||
const inputs = form.querySelectorAll('input, textarea, select');
|
|
||||||
let isValid = true;
|
|
||||||
|
|
||||||
inputs.forEach(input => {
|
|
||||||
const errorElement = document.getElementById(`${input.id}-error`);
|
|
||||||
|
|
||||||
// Clear previous errors
|
|
||||||
input.classList.remove('is-invalid');
|
|
||||||
if (errorElement) {
|
|
||||||
errorElement.textContent = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate required fields
|
|
||||||
if (input.hasAttribute('required') && !input.value.trim()) {
|
|
||||||
showFieldError(input, 'This field is required');
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate specific field types
|
|
||||||
if (input.type === 'email' && input.value && !isValidEmail(input.value)) {
|
|
||||||
showFieldError(input, 'Please enter a valid email address');
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.type === 'url' && input.value && !isValidURL(input.value)) {
|
|
||||||
showFieldError(input, 'Please enter a valid URL');
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
function showFieldError(input, message) {
|
|
||||||
input.classList.add('is-invalid');
|
|
||||||
const errorElement = document.getElementById(`${input.id}-error`);
|
|
||||||
if (errorElement) {
|
|
||||||
errorElement.textContent = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isValidEmail(email) {
|
|
||||||
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
||||||
return regex.test(email);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isValidURL(url) {
|
|
||||||
try {
|
|
||||||
new URL(url);
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling Patterns
|
|
||||||
|
|
||||||
### User-Friendly Error Display
|
|
||||||
```javascript
|
|
||||||
function showError(message, type = 'error') {
|
|
||||||
const errorContainer = document.getElementById('errorContainer');
|
|
||||||
const errorElement = document.createElement('div');
|
|
||||||
errorElement.className = `alert alert-${type} alert-dismissible`;
|
|
||||||
errorElement.innerHTML = `
|
|
||||||
<span class="alert-icon">⚠️</span>
|
|
||||||
<span class="alert-message">${message}</span>
|
|
||||||
<button type="button" class="alert-close" onclick="this.parentElement.remove()">
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
`;
|
|
||||||
|
|
||||||
errorContainer.appendChild(errorElement);
|
|
||||||
|
|
||||||
// Auto-dismiss after 5 seconds
|
|
||||||
setTimeout(() => {
|
|
||||||
if (errorElement.parentNode) {
|
|
||||||
errorElement.remove();
|
|
||||||
}
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showSuccess(message) {
|
|
||||||
showError(message, 'success');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing Requirements
|
|
||||||
|
|
||||||
### UI/UX Testing Checklist
|
|
||||||
- [ ] Test widget responsive behavior on different screen sizes
|
|
||||||
- [ ] Verify wallet balance updates in real-time
|
|
||||||
- [ ] Test form validation with various input combinations
|
|
||||||
- [ ] Verify accessibility with screen reader
|
|
||||||
- [ ] Test keyboard navigation through all interactive elements
|
|
||||||
- [ ] Check quick agent access panel functionality
|
|
||||||
- [ ] Verify error handling and user feedback
|
|
||||||
- [ ] Test performance with large outputs
|
|
||||||
|
|
||||||
### Security Testing
|
|
||||||
- [ ] Test XSS prevention with malicious inputs
|
|
||||||
- [ ] Verify HTML sanitization functions
|
|
||||||
- [ ] Test CSRF protection
|
|
||||||
- [ ] Check for sensitive data exposure
|
|
||||||
- [ ] Validate input sanitization
|
|
||||||
|
|
||||||
## Deployment Checklist
|
|
||||||
|
|
||||||
### Pre-Deployment
|
|
||||||
- [ ] Run all tests and ensure they pass
|
|
||||||
- [ ] Check for console errors
|
|
||||||
- [ ] Verify accessibility compliance
|
|
||||||
- [ ] Test on multiple browsers
|
|
||||||
- [ ] Validate HTML and CSS
|
|
||||||
- [ ] Check for unused CSS/JS
|
|
||||||
- [ ] Optimize images and assets
|
|
||||||
- [ ] Test with real user data
|
|
||||||
|
|
||||||
### Post-Deployment
|
|
||||||
- [ ] Monitor for JavaScript errors
|
|
||||||
- [ ] Check wallet balance functionality
|
|
||||||
- [ ] Verify agent processing works correctly
|
|
||||||
- [ ] Test performance metrics
|
|
||||||
- [ ] Monitor user feedback
|
|
||||||
- [ ] Check analytics for usage patterns
|
|
||||||
|
|
||||||
## Maintenance Guidelines
|
|
||||||
|
|
||||||
### Regular Updates
|
|
||||||
- Keep dependencies updated
|
|
||||||
- Monitor for security vulnerabilities
|
|
||||||
- Update accessibility standards
|
|
||||||
- Review performance metrics
|
|
||||||
- Update documentation
|
|
||||||
|
|
||||||
### Code Quality
|
|
||||||
- Follow consistent coding standards
|
|
||||||
- Use proper commenting
|
|
||||||
- Implement proper error handling
|
|
||||||
- Add comprehensive tests
|
|
||||||
- Regular code reviews
|
|
||||||
|
|
||||||
This guide ensures that all future agents follow the same optimized patterns, preventing UI/UX failures and maintaining consistency across the platform.
|
|
||||||
@ -1,266 +0,0 @@
|
|||||||
# Payment System Documentation
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
NetCop Hub uses a Stripe-based payment system with API verification for reliable, instant wallet top-ups. The system bypasses webhook dependencies by verifying payments directly with the Stripe API when users return from successful payments.
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### API-Based Verification (Current Implementation)
|
|
||||||
|
|
||||||
Instead of relying on webhooks, the system uses direct API calls for payment verification:
|
|
||||||
|
|
||||||
```
|
|
||||||
User Payment Flow:
|
|
||||||
1. User selects amount → Stripe checkout session created
|
|
||||||
2. User pays on Stripe → Returns to success page with session_id
|
|
||||||
3. Success page calls Stripe API → Verifies payment status
|
|
||||||
4. If paid → Wallet balance updated immediately
|
|
||||||
5. User sees instant confirmation
|
|
||||||
```
|
|
||||||
|
|
||||||
## Setup Guide
|
|
||||||
|
|
||||||
### 1. Stripe Account Setup
|
|
||||||
|
|
||||||
1. **Create Stripe Account**: https://dashboard.stripe.com/register
|
|
||||||
2. **Get API Keys**:
|
|
||||||
- Go to Dashboard → Developers → API keys
|
|
||||||
- Copy **Publishable key** (starts with `pk_test_`)
|
|
||||||
- Copy **Secret key** (starts with `sk_test_`)
|
|
||||||
|
|
||||||
### 2. Environment Configuration
|
|
||||||
|
|
||||||
Add to your `.env` file:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Stripe Configuration
|
|
||||||
STRIPE_SECRET_KEY=sk_test_your_secret_key_here
|
|
||||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key_here
|
|
||||||
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret_here # Optional
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Django Settings
|
|
||||||
|
|
||||||
The settings are automatically configured in `settings.py`:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Stripe Configuration (automatically loaded from .env)
|
|
||||||
STRIPE_SECRET_KEY = os.getenv('STRIPE_SECRET_KEY')
|
|
||||||
STRIPE_PUBLISHABLE_KEY = os.getenv('NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY')
|
|
||||||
STRIPE_WEBHOOK_SECRET = os.getenv('STRIPE_WEBHOOK_SECRET')
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Railway Deployment
|
|
||||||
|
|
||||||
Add environment variables in Railway dashboard:
|
|
||||||
|
|
||||||
1. Go to your Railway project
|
|
||||||
2. Navigate to Variables tab
|
|
||||||
3. Add:
|
|
||||||
- `STRIPE_SECRET_KEY` = `sk_test_...`
|
|
||||||
- `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` = `pk_test_...`
|
|
||||||
- `STRIPE_WEBHOOK_SECRET` = `whsec_...` (optional)
|
|
||||||
|
|
||||||
## Payment Flow Details
|
|
||||||
|
|
||||||
### 1. Checkout Session Creation
|
|
||||||
|
|
||||||
**File**: `wallet/stripe_handler.py`
|
|
||||||
|
|
||||||
```python
|
|
||||||
def create_checkout_session(self, user, amount, request=None):
|
|
||||||
# Creates Stripe checkout session
|
|
||||||
# Includes user metadata and success URL with session_id parameter
|
|
||||||
# Returns payment URL for redirect
|
|
||||||
```
|
|
||||||
|
|
||||||
**Features**:
|
|
||||||
- Validates allowed amounts (10, 50, 100, 500 AED)
|
|
||||||
- Includes comprehensive metadata
|
|
||||||
- Auto-expires after 30 minutes
|
|
||||||
- Immediate verification after creation
|
|
||||||
|
|
||||||
### 2. Payment Verification
|
|
||||||
|
|
||||||
**File**: `core/views.py` - `wallet_topup_success_view()`
|
|
||||||
|
|
||||||
```python
|
|
||||||
def wallet_topup_success_view(request):
|
|
||||||
session_id = request.GET.get('session_id')
|
|
||||||
# Verify payment with Stripe API
|
|
||||||
# Update wallet balance if successful
|
|
||||||
# Show confirmation message
|
|
||||||
```
|
|
||||||
|
|
||||||
**Process**:
|
|
||||||
1. Extract `session_id` from URL parameters
|
|
||||||
2. Call `stripe.checkout.Session.retrieve(session_id)`
|
|
||||||
3. Check if `payment_status == 'paid'` and `status == 'complete'`
|
|
||||||
4. Update user wallet balance
|
|
||||||
5. Create transaction record
|
|
||||||
6. Redirect to wallet with success message
|
|
||||||
|
|
||||||
### 3. Error Handling
|
|
||||||
|
|
||||||
- **Missing session_id**: Shows error, redirects to wallet
|
|
||||||
- **Payment not completed**: Shows warning with instructions
|
|
||||||
- **API errors**: Graceful error handling with user-friendly messages
|
|
||||||
- **Duplicate processing**: Prevents double-charging with session ID checks
|
|
||||||
|
|
||||||
## API Endpoints
|
|
||||||
|
|
||||||
### Core Wallet URLs
|
|
||||||
|
|
||||||
- `GET /wallet/` - Wallet dashboard and transaction history
|
|
||||||
- `GET /wallet/topup/` - Payment amount selection page
|
|
||||||
- `POST /wallet/topup/` - Create Stripe checkout session
|
|
||||||
- `GET /wallet/top-up/success/?session_id=cs_...` - Payment verification
|
|
||||||
- `GET /wallet/top-up/cancel/` - Payment cancellation handling
|
|
||||||
|
|
||||||
### Debug Endpoints (Development)
|
|
||||||
|
|
||||||
- `GET /stripe/debug/` - Test Stripe API connectivity and account info
|
|
||||||
- `POST /stripe/webhook/` - Webhook endpoint (backup, not actively used)
|
|
||||||
|
|
||||||
## Database Schema
|
|
||||||
|
|
||||||
### WalletTransaction Model
|
|
||||||
|
|
||||||
```python
|
|
||||||
class WalletTransaction(models.Model):
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
||||||
amount = models.DecimalField(max_digits=10, decimal_places=2)
|
|
||||||
type = models.CharField(max_length=20) # 'top_up' or 'agent_usage'
|
|
||||||
description = models.CharField(max_length=255)
|
|
||||||
stripe_session_id = models.CharField(max_length=255, blank=True)
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
```
|
|
||||||
|
|
||||||
### User Wallet Methods
|
|
||||||
|
|
||||||
```python
|
|
||||||
class User(AbstractUser):
|
|
||||||
wallet_balance = models.DecimalField(max_digits=10, decimal_places=2, default=0)
|
|
||||||
|
|
||||||
def add_balance(self, amount, description, stripe_session_id=None):
|
|
||||||
# Adds money to wallet and creates transaction record
|
|
||||||
|
|
||||||
def deduct_balance(self, amount, description, agent_slug):
|
|
||||||
# Removes money for agent usage
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
### Test Payment Flow
|
|
||||||
|
|
||||||
1. **Local Testing**:
|
|
||||||
```bash
|
|
||||||
python manage.py runserver
|
|
||||||
# Visit http://localhost:8000/wallet/topup/
|
|
||||||
# Use test card: 4242 4242 4242 4242
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Stripe Test Cards**:
|
|
||||||
- **Success**: `4242 4242 4242 4242`
|
|
||||||
- **Decline**: `4000 0000 0000 0002`
|
|
||||||
- **Requires authentication**: `4000 0025 0000 3155`
|
|
||||||
|
|
||||||
3. **Debugging**:
|
|
||||||
- Visit `/stripe/debug/` to test API connectivity
|
|
||||||
- Check Railway logs for payment verification details
|
|
||||||
- Monitor Stripe dashboard for session creation
|
|
||||||
|
|
||||||
### Test Scenarios
|
|
||||||
|
|
||||||
- ✅ **Successful payment**: Amount added, transaction recorded
|
|
||||||
- ✅ **Cancelled payment**: No charge, user returned to form
|
|
||||||
- ✅ **Duplicate session**: Prevents double-charging
|
|
||||||
- ✅ **Network errors**: Graceful error handling
|
|
||||||
- ✅ **Invalid session**: Error message with support contact
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
|
|
||||||
1. **"No session found"**:
|
|
||||||
- Check if session_id parameter is in success URL
|
|
||||||
- Verify Stripe API keys are correct
|
|
||||||
- Check Railway environment variables
|
|
||||||
|
|
||||||
2. **"Payment verification failed"**:
|
|
||||||
- Confirm payment was completed on Stripe
|
|
||||||
- Check Stripe dashboard for payment status
|
|
||||||
- Verify API version compatibility
|
|
||||||
|
|
||||||
3. **"Session already processed"**:
|
|
||||||
- Normal behavior - prevents double-charging
|
|
||||||
- User balance was already updated
|
|
||||||
|
|
||||||
### Debug Steps
|
|
||||||
|
|
||||||
1. **Check Stripe Configuration**:
|
|
||||||
```bash
|
|
||||||
# Visit debug endpoint
|
|
||||||
curl https://your-app.up.railway.app/stripe/debug/
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Verify Environment Variables**:
|
|
||||||
```bash
|
|
||||||
# In Railway dashboard, check Variables tab
|
|
||||||
# Ensure all Stripe keys are set correctly
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Monitor Logs**:
|
|
||||||
```bash
|
|
||||||
# Railway logs show detailed payment verification
|
|
||||||
# Look for "[STRIPE DEBUG]" messages
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security Considerations
|
|
||||||
|
|
||||||
### API Key Security
|
|
||||||
- ✅ **Secret keys**: Stored in environment variables, never in code
|
|
||||||
- ✅ **Publishable keys**: Safe to expose in frontend
|
|
||||||
- ✅ **Test vs Live**: Always use test keys for development
|
|
||||||
|
|
||||||
### Payment Security
|
|
||||||
- ✅ **Amount validation**: Only allows predefined amounts (10, 50, 100, 500)
|
|
||||||
- ✅ **User authentication**: All payment endpoints require login
|
|
||||||
- ✅ **Session verification**: Direct API verification prevents tampering
|
|
||||||
- ✅ **Duplicate prevention**: Session ID tracking prevents double-charging
|
|
||||||
|
|
||||||
### Data Protection
|
|
||||||
- ✅ **No sensitive data storage**: Credit card info handled by Stripe
|
|
||||||
- ✅ **Transaction records**: Only store metadata and amounts
|
|
||||||
- ✅ **User privacy**: Email and user ID properly associated
|
|
||||||
|
|
||||||
## Advantages of This Approach
|
|
||||||
|
|
||||||
### vs Webhooks
|
|
||||||
- **Reliability**: No webhook delivery failures
|
|
||||||
- **Speed**: Instant verification when user returns
|
|
||||||
- **Debugging**: Easier to trace and debug payment flows
|
|
||||||
- **User Experience**: Immediate feedback and balance updates
|
|
||||||
|
|
||||||
### vs Frontend-Only
|
|
||||||
- **Security**: Server-side verification prevents tampering
|
|
||||||
- **Reliability**: Works even if frontend JavaScript fails
|
|
||||||
- **Data Integrity**: Database updates happen server-side
|
|
||||||
|
|
||||||
### Production Ready
|
|
||||||
- **Scalability**: API calls scale better than webhook processing
|
|
||||||
- **Monitoring**: Easier to monitor and alert on payment issues
|
|
||||||
- **Maintenance**: Simpler codebase without webhook infrastructure
|
|
||||||
|
|
||||||
## Migration from Webhook System
|
|
||||||
|
|
||||||
If migrating from a webhook-based system:
|
|
||||||
|
|
||||||
1. **Remove webhook endpoints** and processing code
|
|
||||||
2. **Update success URLs** to include `{CHECKOUT_SESSION_ID}` parameter
|
|
||||||
3. **Implement verification** in success page handler
|
|
||||||
4. **Test thoroughly** with test payments
|
|
||||||
5. **Monitor logs** during transition period
|
|
||||||
|
|
||||||
The API-based approach is more reliable and provides better user experience than webhook-dependent systems.
|
|
||||||
@ -1,244 +0,0 @@
|
|||||||
# PostgreSQL Local Development Setup
|
|
||||||
|
|
||||||
## Why Use PostgreSQL Locally?
|
|
||||||
|
|
||||||
Using PostgreSQL locally matches your Railway production environment and prevents deployment failures caused by database engine differences.
|
|
||||||
|
|
||||||
## Quick Setup (Option 1: Docker - Easiest)
|
|
||||||
|
|
||||||
### 1. Install Docker
|
|
||||||
Download Docker Desktop from: https://www.docker.com/products/docker-desktop/
|
|
||||||
|
|
||||||
### 2. Run PostgreSQL Container
|
|
||||||
```bash
|
|
||||||
# Create and start PostgreSQL container
|
|
||||||
docker run --name netcop-postgres \
|
|
||||||
-e POSTGRES_DB=netcop_hub \
|
|
||||||
-e POSTGRES_USER=netcop_user \
|
|
||||||
-e POSTGRES_PASSWORD=netcop_pass \
|
|
||||||
-p 5432:5432 \
|
|
||||||
-d postgres:15
|
|
||||||
|
|
||||||
# Verify it's running
|
|
||||||
docker ps
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Update Your .env File
|
|
||||||
The `.env` file is already configured for this setup:
|
|
||||||
```env
|
|
||||||
DATABASE_URL=postgresql://netcop_user:netcop_pass@localhost:5432/netcop_hub
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Start/Stop Database
|
|
||||||
```bash
|
|
||||||
# Start the database (if stopped)
|
|
||||||
docker start netcop-postgres
|
|
||||||
|
|
||||||
# Stop the database (when not needed)
|
|
||||||
docker stop netcop-postgres
|
|
||||||
|
|
||||||
# View logs (for debugging)
|
|
||||||
docker logs netcop-postgres
|
|
||||||
```
|
|
||||||
|
|
||||||
## Full Setup (Option 2: Native PostgreSQL)
|
|
||||||
|
|
||||||
### 1. Install PostgreSQL
|
|
||||||
|
|
||||||
**macOS (with Homebrew):**
|
|
||||||
```bash
|
|
||||||
brew install postgresql@15
|
|
||||||
brew services start postgresql@15
|
|
||||||
```
|
|
||||||
|
|
||||||
**Ubuntu/Debian:**
|
|
||||||
```bash
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install postgresql postgresql-contrib
|
|
||||||
sudo systemctl start postgresql
|
|
||||||
sudo systemctl enable postgresql
|
|
||||||
```
|
|
||||||
|
|
||||||
**Windows:**
|
|
||||||
Download from: https://www.postgresql.org/download/windows/
|
|
||||||
|
|
||||||
### 2. Create Database and User
|
|
||||||
```bash
|
|
||||||
# Connect to PostgreSQL as superuser
|
|
||||||
sudo -u postgres psql
|
|
||||||
|
|
||||||
# Or on macOS/Windows:
|
|
||||||
psql postgres
|
|
||||||
|
|
||||||
# Create database and user
|
|
||||||
CREATE DATABASE netcop_hub;
|
|
||||||
CREATE USER netcop_user WITH PASSWORD 'netcop_pass';
|
|
||||||
GRANT ALL PRIVILEGES ON DATABASE netcop_hub TO netcop_user;
|
|
||||||
\q
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Test Connection
|
|
||||||
```bash
|
|
||||||
psql -h localhost -U netcop_user -d netcop_hub
|
|
||||||
# Enter password: netcop_pass
|
|
||||||
# You should see: netcop_hub=>
|
|
||||||
\q
|
|
||||||
```
|
|
||||||
|
|
||||||
## Django Setup
|
|
||||||
|
|
||||||
### 1. Install PostgreSQL Python Driver
|
|
||||||
```bash
|
|
||||||
pip install psycopg2-binary
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Reset Migrations (Clean Start)
|
|
||||||
```bash
|
|
||||||
# Reset all migrations for clean PostgreSQL setup
|
|
||||||
python manage.py reset_database --action full --confirm
|
|
||||||
|
|
||||||
# Or manually:
|
|
||||||
python manage.py reset_database --action migrations --confirm
|
|
||||||
python manage.py makemigrations
|
|
||||||
python manage.py migrate
|
|
||||||
python manage.py populate_agents --create-admin
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Test Your Setup
|
|
||||||
```bash
|
|
||||||
# Check database connection
|
|
||||||
python manage.py backup_users --action info
|
|
||||||
|
|
||||||
# Create test user
|
|
||||||
python manage.py create_user test@example.com testpass123 --balance 50
|
|
||||||
|
|
||||||
# Start development server
|
|
||||||
python manage.py runserver
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Connection Refused Error
|
|
||||||
```
|
|
||||||
psycopg2.OperationalError: could not connect to server: Connection refused
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
- Ensure PostgreSQL is running: `docker ps` or `brew services list`
|
|
||||||
- Check port 5432 is not in use: `lsof -i :5432`
|
|
||||||
- For Docker: `docker start netcop-postgres`
|
|
||||||
|
|
||||||
### Password Authentication Failed
|
|
||||||
```
|
|
||||||
psycopg2.OperationalError: FATAL: password authentication failed
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
- Check `.env` file has correct credentials
|
|
||||||
- Recreate user with correct password:
|
|
||||||
```sql
|
|
||||||
DROP USER IF EXISTS netcop_user;
|
|
||||||
CREATE USER netcop_user WITH PASSWORD 'netcop_pass';
|
|
||||||
GRANT ALL PRIVILEGES ON DATABASE netcop_hub TO netcop_user;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Migration Conflicts
|
|
||||||
```
|
|
||||||
django.db.utils.ProgrammingError: column "data_file" already exists
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
```bash
|
|
||||||
# Fix migration conflicts
|
|
||||||
python manage.py fix_migrations --app data_analyzer
|
|
||||||
|
|
||||||
# Or clean reset
|
|
||||||
python manage.py reset_database --action full --confirm
|
|
||||||
```
|
|
||||||
|
|
||||||
### Database Permission Denied
|
|
||||||
```
|
|
||||||
django.db.utils.ProgrammingError: permission denied for relation
|
|
||||||
```
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
```sql
|
|
||||||
# Grant all permissions to user
|
|
||||||
GRANT ALL PRIVILEGES ON DATABASE netcop_hub TO netcop_user;
|
|
||||||
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO netcop_user;
|
|
||||||
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO netcop_user;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development Workflow
|
|
||||||
|
|
||||||
### Daily Workflow
|
|
||||||
```bash
|
|
||||||
# 1. Start database (Docker)
|
|
||||||
docker start netcop-postgres
|
|
||||||
|
|
||||||
# 2. Start Django development server
|
|
||||||
python manage.py runserver
|
|
||||||
|
|
||||||
# 3. When done, stop database (optional)
|
|
||||||
docker stop netcop-postgres
|
|
||||||
```
|
|
||||||
|
|
||||||
### Making Model Changes
|
|
||||||
```bash
|
|
||||||
# 1. Edit your models.py
|
|
||||||
# 2. Create migrations
|
|
||||||
python manage.py makemigrations
|
|
||||||
|
|
||||||
# 3. Test migration locally (PostgreSQL)
|
|
||||||
python manage.py migrate
|
|
||||||
|
|
||||||
# 4. Test your changes
|
|
||||||
python manage.py runserver
|
|
||||||
|
|
||||||
# 5. Commit and push (will deploy to Railway)
|
|
||||||
git add .
|
|
||||||
git commit -m "Update models"
|
|
||||||
git push origin main
|
|
||||||
```
|
|
||||||
|
|
||||||
### Switching Between SQLite and PostgreSQL
|
|
||||||
|
|
||||||
**To use SQLite (quick testing):**
|
|
||||||
```env
|
|
||||||
# In .env file:
|
|
||||||
DATABASE_URL=sqlite:///db.sqlite3
|
|
||||||
```
|
|
||||||
|
|
||||||
**To use PostgreSQL (development/production parity):**
|
|
||||||
```env
|
|
||||||
# In .env file:
|
|
||||||
DATABASE_URL=postgresql://netcop_user:netcop_pass@localhost:5432/netcop_hub
|
|
||||||
```
|
|
||||||
|
|
||||||
## Benefits You'll See
|
|
||||||
|
|
||||||
✅ **Reliable deployments** - What works locally works on Railway
|
|
||||||
✅ **Early error detection** - Catch PostgreSQL-specific issues
|
|
||||||
✅ **Consistent behavior** - Same database engine everywhere
|
|
||||||
✅ **Better performance testing** - Real PostgreSQL performance
|
|
||||||
✅ **Migration confidence** - Test exact same migrations
|
|
||||||
|
|
||||||
## Quick Commands Reference
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Database management
|
|
||||||
python manage.py backup_users --action info
|
|
||||||
python manage.py reset_database --action full --confirm
|
|
||||||
python manage.py fix_migrations --check-only
|
|
||||||
|
|
||||||
# User management
|
|
||||||
python manage.py create_user email@example.com password123 --superuser
|
|
||||||
python manage.py populate_agents --create-admin
|
|
||||||
|
|
||||||
# Docker PostgreSQL
|
|
||||||
docker start netcop-postgres
|
|
||||||
docker stop netcop-postgres
|
|
||||||
docker logs netcop-postgres
|
|
||||||
```
|
|
||||||
|
|
||||||
Your development environment now matches Railway production exactly! 🎉
|
|
||||||
@ -1,520 +0,0 @@
|
|||||||
# Pre-Implementation Analysis Protocol
|
|
||||||
|
|
||||||
A comprehensive 3-phase approach to ensure thorough analysis before making any template changes.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This protocol prevents the failures that occurred with the Social Ads Generator by ensuring complete understanding of requirements and existing implementations before any code changes are made.
|
|
||||||
|
|
||||||
## Phase 1: Complete Requirements Analysis
|
|
||||||
|
|
||||||
### 1.1 User Requirements Deep Dive
|
|
||||||
|
|
||||||
**Requirements Extraction Process:**
|
|
||||||
1. **Read the entire user request** multiple times
|
|
||||||
2. **Identify explicit requirements** (what they directly stated)
|
|
||||||
3. **Identify implicit requirements** (what they likely mean)
|
|
||||||
4. **Ask clarifying questions** if anything is unclear
|
|
||||||
5. **Document all requirements** in a structured format
|
|
||||||
|
|
||||||
**Requirements Documentation Template:**
|
|
||||||
```markdown
|
|
||||||
# Requirements Analysis: [Task Name]
|
|
||||||
|
|
||||||
## Explicit Requirements
|
|
||||||
- [ ] Requirement 1: [Description]
|
|
||||||
- [ ] Requirement 2: [Description]
|
|
||||||
- [ ] Requirement 3: [Description]
|
|
||||||
|
|
||||||
## Implicit Requirements
|
|
||||||
- [ ] Implied Requirement 1: [Description and reasoning]
|
|
||||||
- [ ] Implied Requirement 2: [Description and reasoning]
|
|
||||||
|
|
||||||
## Success Criteria
|
|
||||||
- [ ] Visual: [What should it look like?]
|
|
||||||
- [ ] Functional: [How should it behave?]
|
|
||||||
- [ ] Technical: [What technical standards must be met?]
|
|
||||||
|
|
||||||
## Constraints
|
|
||||||
- [ ] Technical constraints: [List]
|
|
||||||
- [ ] Design constraints: [List]
|
|
||||||
- [ ] Performance constraints: [List]
|
|
||||||
|
|
||||||
## Questions for Clarification
|
|
||||||
- [ ] Question 1: [What needs clarification?]
|
|
||||||
- [ ] Question 2: [What assumptions need verification?]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.2 Context Understanding
|
|
||||||
|
|
||||||
**Environmental Analysis:**
|
|
||||||
- [ ] What is the current state of the target system?
|
|
||||||
- [ ] What other templates exist that might be relevant?
|
|
||||||
- [ ] What is the overall design system and architecture?
|
|
||||||
- [ ] What are the user's expectations based on past interactions?
|
|
||||||
- [ ] What are the business requirements and constraints?
|
|
||||||
|
|
||||||
**Stakeholder Analysis:**
|
|
||||||
- [ ] Who is the primary user?
|
|
||||||
- [ ] What is their technical expertise level?
|
|
||||||
- [ ] What are their preferences and priorities?
|
|
||||||
- [ ] What is their tolerance for iterative changes?
|
|
||||||
- [ ] What is their timeline and urgency level?
|
|
||||||
|
|
||||||
### 1.3 Scope Definition
|
|
||||||
|
|
||||||
**Scope Boundary Documentation:**
|
|
||||||
```markdown
|
|
||||||
# Scope Definition: [Task Name]
|
|
||||||
|
|
||||||
## In Scope
|
|
||||||
- [ ] Specific changes to be made
|
|
||||||
- [ ] Components to be modified
|
|
||||||
- [ ] Features to be implemented
|
|
||||||
- [ ] Standards to be followed
|
|
||||||
|
|
||||||
## Out of Scope
|
|
||||||
- [ ] Changes not requested
|
|
||||||
- [ ] Components not to be modified
|
|
||||||
- [ ] Features not to be implemented
|
|
||||||
- [ ] Standards not applicable
|
|
||||||
|
|
||||||
## Assumptions
|
|
||||||
- [ ] Assumption 1: [Description]
|
|
||||||
- [ ] Assumption 2: [Description]
|
|
||||||
- [ ] Assumption 3: [Description]
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
- [ ] External dependencies
|
|
||||||
- [ ] Internal dependencies
|
|
||||||
- [ ] Technical dependencies
|
|
||||||
- [ ] Resource dependencies
|
|
||||||
```
|
|
||||||
|
|
||||||
## Phase 2: Complete Reference Analysis
|
|
||||||
|
|
||||||
### 2.1 Source Template Deep Analysis
|
|
||||||
|
|
||||||
**Comprehensive Template Reading Protocol:**
|
|
||||||
1. **Read the entire template** from start to finish
|
|
||||||
2. **Create a mental model** of the overall structure
|
|
||||||
3. **Re-read focusing on specific sections** (HTML, CSS, JS)
|
|
||||||
4. **Document every component** and its purpose
|
|
||||||
5. **Map relationships** between components
|
|
||||||
6. **Identify patterns** and conventions used
|
|
||||||
|
|
||||||
**Template Analysis Worksheet:**
|
|
||||||
```markdown
|
|
||||||
# Template Analysis: [Template Name]
|
|
||||||
|
|
||||||
## Overall Structure
|
|
||||||
- **Template extends**: [Base template]
|
|
||||||
- **Block structure**: [List of Django blocks]
|
|
||||||
- **Main sections**: [List of major sections]
|
|
||||||
- **Total lines**: [Count]
|
|
||||||
- **Complexity level**: [Low/Medium/High]
|
|
||||||
|
|
||||||
## HTML Structure Analysis
|
|
||||||
### Header Section
|
|
||||||
- **Elements**: [List all elements]
|
|
||||||
- **Classes**: [List all CSS classes]
|
|
||||||
- **IDs**: [List all HTML IDs]
|
|
||||||
- **ARIA attributes**: [List accessibility attributes]
|
|
||||||
- **Structure pattern**: [Describe the layout pattern]
|
|
||||||
|
|
||||||
### Main Content Section
|
|
||||||
- **Elements**: [List all elements]
|
|
||||||
- **Classes**: [List all CSS classes]
|
|
||||||
- **IDs**: [List all HTML IDs]
|
|
||||||
- **Form elements**: [List all form elements]
|
|
||||||
- **Structure pattern**: [Describe the layout pattern]
|
|
||||||
|
|
||||||
### Sidebar Section
|
|
||||||
- **Elements**: [List all elements]
|
|
||||||
- **Classes**: [List all CSS classes]
|
|
||||||
- **IDs**: [List all HTML IDs]
|
|
||||||
- **Widget structure**: [Describe widget organization]
|
|
||||||
- **Structure pattern**: [Describe the layout pattern]
|
|
||||||
|
|
||||||
### Footer/Additional Sections
|
|
||||||
- **Elements**: [List all elements]
|
|
||||||
- **Classes**: [List all CSS classes]
|
|
||||||
- **IDs**: [List all HTML IDs]
|
|
||||||
- **Purpose**: [Describe purpose of each section]
|
|
||||||
|
|
||||||
## CSS Architecture Analysis
|
|
||||||
### Design Tokens
|
|
||||||
- **Color variables**: [List all color variables]
|
|
||||||
- **Spacing variables**: [List all spacing variables]
|
|
||||||
- **Typography variables**: [List all typography variables]
|
|
||||||
- **Border radius variables**: [List all radius variables]
|
|
||||||
- **Shadow variables**: [List all shadow variables]
|
|
||||||
|
|
||||||
### Component Classes
|
|
||||||
- **Layout classes**: [List classes for layout]
|
|
||||||
- **Component classes**: [List classes for components]
|
|
||||||
- **State classes**: [List classes for states]
|
|
||||||
- **Utility classes**: [List utility classes]
|
|
||||||
|
|
||||||
### Responsive Design
|
|
||||||
- **Breakpoints**: [List all media queries]
|
|
||||||
- **Mobile changes**: [List mobile-specific changes]
|
|
||||||
- **Tablet changes**: [List tablet-specific changes]
|
|
||||||
- **Desktop changes**: [List desktop-specific changes]
|
|
||||||
|
|
||||||
## JavaScript Analysis
|
|
||||||
### Functions
|
|
||||||
- **Function 1**: [Name, purpose, parameters, return value]
|
|
||||||
- **Function 2**: [Name, purpose, parameters, return value]
|
|
||||||
- **Function 3**: [Name, purpose, parameters, return value]
|
|
||||||
|
|
||||||
### Event Listeners
|
|
||||||
- **Event 1**: [Element, event type, handler function]
|
|
||||||
- **Event 2**: [Element, event type, handler function]
|
|
||||||
- **Event 3**: [Element, event type, handler function]
|
|
||||||
|
|
||||||
### DOM Manipulation
|
|
||||||
- **Elements modified**: [List elements that are modified]
|
|
||||||
- **Classes added/removed**: [List dynamic class changes]
|
|
||||||
- **Content updates**: [List content that gets updated]
|
|
||||||
|
|
||||||
### State Management
|
|
||||||
- **Global variables**: [List global variables]
|
|
||||||
- **State tracking**: [List state variables]
|
|
||||||
- **Data flow**: [Describe how data flows]
|
|
||||||
|
|
||||||
## Interaction Patterns
|
|
||||||
### User Interactions
|
|
||||||
- **Click interactions**: [List all clickable elements]
|
|
||||||
- **Form interactions**: [List all form interactions]
|
|
||||||
- **Hover effects**: [List all hover effects]
|
|
||||||
- **Focus management**: [List focus management]
|
|
||||||
|
|
||||||
### System Interactions
|
|
||||||
- **API calls**: [List all API interactions]
|
|
||||||
- **Data persistence**: [List data storage/retrieval]
|
|
||||||
- **External integrations**: [List external integrations]
|
|
||||||
|
|
||||||
## Performance Considerations
|
|
||||||
- **Loading performance**: [Notes on loading speed]
|
|
||||||
- **Runtime performance**: [Notes on runtime efficiency]
|
|
||||||
- **Memory usage**: [Notes on memory consumption]
|
|
||||||
- **Network requests**: [List all network requests]
|
|
||||||
|
|
||||||
## Security Considerations
|
|
||||||
- **Input validation**: [List input validation measures]
|
|
||||||
- **Output sanitization**: [List output sanitization measures]
|
|
||||||
- **XSS prevention**: [List XSS prevention measures]
|
|
||||||
- **CSRF protection**: [List CSRF protection measures]
|
|
||||||
|
|
||||||
## Accessibility Features
|
|
||||||
- **ARIA attributes**: [List all ARIA attributes]
|
|
||||||
- **Keyboard navigation**: [List keyboard navigation support]
|
|
||||||
- **Screen reader support**: [List screen reader features]
|
|
||||||
- **Color contrast**: [Notes on color contrast]
|
|
||||||
|
|
||||||
## Browser Compatibility
|
|
||||||
- **Supported browsers**: [List supported browsers]
|
|
||||||
- **Fallbacks**: [List fallback implementations]
|
|
||||||
- **Polyfills**: [List polyfills used]
|
|
||||||
- **Progressive enhancement**: [List progressive enhancement features]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 Target Template Current State Analysis
|
|
||||||
|
|
||||||
**Current State Documentation:**
|
|
||||||
```markdown
|
|
||||||
# Current State Analysis: [Target Template]
|
|
||||||
|
|
||||||
## Existing Implementation
|
|
||||||
- **Current approach**: [Describe current implementation]
|
|
||||||
- **Strengths**: [List what works well]
|
|
||||||
- **Weaknesses**: [List what doesn't work well]
|
|
||||||
- **Technical debt**: [List technical debt items]
|
|
||||||
|
|
||||||
## Component Inventory
|
|
||||||
- **Existing components**: [List all current components]
|
|
||||||
- **Reusable components**: [List components that can be reused]
|
|
||||||
- **Components to replace**: [List components that need replacement]
|
|
||||||
- **Components to remove**: [List components that should be removed]
|
|
||||||
|
|
||||||
## Code Quality Assessment
|
|
||||||
- **Code organization**: [Assessment of code structure]
|
|
||||||
- **Naming conventions**: [Assessment of naming patterns]
|
|
||||||
- **Documentation**: [Assessment of code documentation]
|
|
||||||
- **Maintainability**: [Assessment of maintainability]
|
|
||||||
|
|
||||||
## Performance Analysis
|
|
||||||
- **Current performance**: [Metrics and observations]
|
|
||||||
- **Bottlenecks**: [Performance bottlenecks identified]
|
|
||||||
- **Optimization opportunities**: [List optimization opportunities]
|
|
||||||
- **Resource usage**: [Current resource usage]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.3 Gap Analysis and Mapping
|
|
||||||
|
|
||||||
**Detailed Gap Analysis:**
|
|
||||||
```markdown
|
|
||||||
# Gap Analysis: [Source] → [Target]
|
|
||||||
|
|
||||||
## Structural Differences
|
|
||||||
| Component | Source | Target | Gap | Priority |
|
|
||||||
|-----------|---------|---------|-----|----------|
|
|
||||||
| Header | [Source structure] | [Target structure] | [Gap description] | [High/Medium/Low] |
|
|
||||||
| Main Content | [Source structure] | [Target structure] | [Gap description] | [High/Medium/Low] |
|
|
||||||
| Sidebar | [Source structure] | [Target structure] | [Gap description] | [High/Medium/Low] |
|
|
||||||
| Footer | [Source structure] | [Target structure] | [Gap description] | [High/Medium/Low] |
|
|
||||||
|
|
||||||
## Styling Differences
|
|
||||||
| Style Category | Source | Target | Gap | Priority |
|
|
||||||
|----------------|---------|---------|-----|----------|
|
|
||||||
| Color Scheme | [Source colors] | [Target colors] | [Gap description] | [High/Medium/Low] |
|
|
||||||
| Typography | [Source typography] | [Target typography] | [Gap description] | [High/Medium/Low] |
|
|
||||||
| Spacing | [Source spacing] | [Target spacing] | [Gap description] | [High/Medium/Low] |
|
|
||||||
| Layout | [Source layout] | [Target layout] | [Gap description] | [High/Medium/Low] |
|
|
||||||
|
|
||||||
## Functional Differences
|
|
||||||
| Functionality | Source | Target | Gap | Priority |
|
|
||||||
|---------------|---------|---------|-----|----------|
|
|
||||||
| User Interactions | [Source interactions] | [Target interactions] | [Gap description] | [High/Medium/Low] |
|
|
||||||
| Data Handling | [Source data handling] | [Target data handling] | [Gap description] | [High/Medium/Low] |
|
|
||||||
| State Management | [Source state] | [Target state] | [Gap description] | [High/Medium/Low] |
|
|
||||||
| Error Handling | [Source errors] | [Target errors] | [Gap description] | [High/Medium/Low] |
|
|
||||||
|
|
||||||
## Missing Elements
|
|
||||||
- [ ] **Missing HTML elements**: [List]
|
|
||||||
- [ ] **Missing CSS classes**: [List]
|
|
||||||
- [ ] **Missing JavaScript functions**: [List]
|
|
||||||
- [ ] **Missing interactions**: [List]
|
|
||||||
- [ ] **Missing accessibility features**: [List]
|
|
||||||
|
|
||||||
## Excessive Elements
|
|
||||||
- [ ] **Unnecessary HTML elements**: [List]
|
|
||||||
- [ ] **Unused CSS classes**: [List]
|
|
||||||
- [ ] **Redundant JavaScript functions**: [List]
|
|
||||||
- [ ] **Unwanted interactions**: [List]
|
|
||||||
- [ ] **Obsolete features**: [List]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Phase 3: Implementation Planning
|
|
||||||
|
|
||||||
### 3.1 Change Strategy Definition
|
|
||||||
|
|
||||||
**Change Strategy Matrix:**
|
|
||||||
```markdown
|
|
||||||
# Change Strategy: [Task Name]
|
|
||||||
|
|
||||||
## Strategic Approach
|
|
||||||
- **Overall strategy**: [Comprehensive rewrite / Incremental updates / Hybrid approach]
|
|
||||||
- **Risk tolerance**: [Low / Medium / High]
|
|
||||||
- **Timeline**: [Immediate / Short-term / Long-term]
|
|
||||||
- **Quality vs. Speed**: [Quality first / Balanced / Speed first]
|
|
||||||
|
|
||||||
## Implementation Approach
|
|
||||||
- **Method**: [Single comprehensive update / Staged implementation / Iterative development]
|
|
||||||
- **Backup strategy**: [Full backup / Incremental backup / Version control]
|
|
||||||
- **Testing approach**: [Comprehensive testing / Targeted testing / Minimal testing]
|
|
||||||
- **Rollback plan**: [Immediate rollback / Staged rollback / No rollback]
|
|
||||||
|
|
||||||
## Resource Requirements
|
|
||||||
- **Time estimate**: [Hours/days required]
|
|
||||||
- **Complexity level**: [Low / Medium / High]
|
|
||||||
- **Risk level**: [Low / Medium / High]
|
|
||||||
- **Dependencies**: [List external dependencies]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 Detailed Implementation Plan
|
|
||||||
|
|
||||||
**Step-by-Step Implementation Plan:**
|
|
||||||
```markdown
|
|
||||||
# Implementation Plan: [Task Name]
|
|
||||||
|
|
||||||
## Phase 1: Foundation
|
|
||||||
### Step 1.1: Environment Setup
|
|
||||||
- [ ] Create backup of current implementation
|
|
||||||
- [ ] Set up development environment
|
|
||||||
- [ ] Prepare testing environment
|
|
||||||
- [ ] Document current state
|
|
||||||
|
|
||||||
### Step 1.2: Structure Preparation
|
|
||||||
- [ ] Analyze HTML structure requirements
|
|
||||||
- [ ] Plan CSS architecture changes
|
|
||||||
- [ ] Design JavaScript function updates
|
|
||||||
- [ ] Prepare component templates
|
|
||||||
|
|
||||||
## Phase 2: Core Implementation
|
|
||||||
### Step 2.1: HTML Structure Updates
|
|
||||||
- [ ] Update header structure
|
|
||||||
- [ ] Modify main content layout
|
|
||||||
- [ ] Adjust sidebar components
|
|
||||||
- [ ] Update footer elements
|
|
||||||
|
|
||||||
### Step 2.2: CSS Architecture Implementation
|
|
||||||
- [ ] Implement design token system
|
|
||||||
- [ ] Update component classes
|
|
||||||
- [ ] Add responsive design rules
|
|
||||||
- [ ] Implement interaction styles
|
|
||||||
|
|
||||||
### Step 2.3: JavaScript Functionality
|
|
||||||
- [ ] Update existing functions
|
|
||||||
- [ ] Add new functions
|
|
||||||
- [ ] Implement event listeners
|
|
||||||
- [ ] Add state management
|
|
||||||
|
|
||||||
## Phase 3: Integration and Testing
|
|
||||||
### Step 3.1: Component Integration
|
|
||||||
- [ ] Test individual components
|
|
||||||
- [ ] Test component interactions
|
|
||||||
- [ ] Verify responsive design
|
|
||||||
- [ ] Check accessibility compliance
|
|
||||||
|
|
||||||
### Step 3.2: System Integration
|
|
||||||
- [ ] Test full system functionality
|
|
||||||
- [ ] Verify performance requirements
|
|
||||||
- [ ] Test cross-browser compatibility
|
|
||||||
- [ ] Validate security measures
|
|
||||||
|
|
||||||
## Phase 4: Validation and Deployment
|
|
||||||
### Step 4.1: Final Validation
|
|
||||||
- [ ] Compare with source template
|
|
||||||
- [ ] Verify all requirements met
|
|
||||||
- [ ] Test user experience
|
|
||||||
- [ ] Document changes made
|
|
||||||
|
|
||||||
### Step 4.2: Deployment
|
|
||||||
- [ ] Deploy to production
|
|
||||||
- [ ] Monitor for issues
|
|
||||||
- [ ] Gather user feedback
|
|
||||||
- [ ] Document lessons learned
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.3 Risk Assessment and Mitigation
|
|
||||||
|
|
||||||
**Risk Analysis:**
|
|
||||||
```markdown
|
|
||||||
# Risk Assessment: [Task Name]
|
|
||||||
|
|
||||||
## High Risk Items
|
|
||||||
### Risk 1: [Risk Description]
|
|
||||||
- **Probability**: [High / Medium / Low]
|
|
||||||
- **Impact**: [High / Medium / Low]
|
|
||||||
- **Mitigation**: [Mitigation strategy]
|
|
||||||
- **Contingency**: [Contingency plan]
|
|
||||||
|
|
||||||
### Risk 2: [Risk Description]
|
|
||||||
- **Probability**: [High / Medium / Low]
|
|
||||||
- **Impact**: [High / Medium / Low]
|
|
||||||
- **Mitigation**: [Mitigation strategy]
|
|
||||||
- **Contingency**: [Contingency plan]
|
|
||||||
|
|
||||||
## Medium Risk Items
|
|
||||||
### Risk 3: [Risk Description]
|
|
||||||
- **Probability**: [High / Medium / Low]
|
|
||||||
- **Impact**: [High / Medium / Low]
|
|
||||||
- **Mitigation**: [Mitigation strategy]
|
|
||||||
- **Contingency**: [Contingency plan]
|
|
||||||
|
|
||||||
## Low Risk Items
|
|
||||||
### Risk 4: [Risk Description]
|
|
||||||
- **Probability**: [High / Medium / Low]
|
|
||||||
- **Impact**: [High / Medium / Low]
|
|
||||||
- **Mitigation**: [Mitigation strategy]
|
|
||||||
- **Contingency**: [Contingency plan]
|
|
||||||
|
|
||||||
## Risk Mitigation Strategies
|
|
||||||
- [ ] **Backup and Recovery**: [Strategy]
|
|
||||||
- [ ] **Incremental Testing**: [Strategy]
|
|
||||||
- [ ] **Rollback Plan**: [Strategy]
|
|
||||||
- [ ] **Monitoring**: [Strategy]
|
|
||||||
- [ ] **Communication**: [Strategy]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Protocol Implementation Checklist
|
|
||||||
|
|
||||||
### Pre-Analysis Phase
|
|
||||||
- [ ] User requirements fully understood
|
|
||||||
- [ ] Context and environment analyzed
|
|
||||||
- [ ] Scope clearly defined
|
|
||||||
- [ ] Stakeholder expectations set
|
|
||||||
|
|
||||||
### Analysis Phase
|
|
||||||
- [ ] Source template completely analyzed
|
|
||||||
- [ ] Target template current state documented
|
|
||||||
- [ ] Gap analysis completed
|
|
||||||
- [ ] Change requirements identified
|
|
||||||
|
|
||||||
### Planning Phase
|
|
||||||
- [ ] Change strategy defined
|
|
||||||
- [ ] Implementation plan created
|
|
||||||
- [ ] Risk assessment completed
|
|
||||||
- [ ] Resource requirements identified
|
|
||||||
|
|
||||||
### Validation Phase
|
|
||||||
- [ ] Analysis validated with stakeholders
|
|
||||||
- [ ] Plan reviewed and approved
|
|
||||||
- [ ] Risks acknowledged and accepted
|
|
||||||
- [ ] Implementation authorized
|
|
||||||
|
|
||||||
## Templates and Tools
|
|
||||||
|
|
||||||
### Requirements Analysis Template
|
|
||||||
```markdown
|
|
||||||
# Requirements Analysis: [Date] - [Task Name]
|
|
||||||
|
|
||||||
## User Request
|
|
||||||
**Original Request**: [Exact quote from user]
|
|
||||||
**Clarifications**: [Any clarifications received]
|
|
||||||
|
|
||||||
## Explicit Requirements
|
|
||||||
1. [Requirement 1]
|
|
||||||
2. [Requirement 2]
|
|
||||||
3. [Requirement 3]
|
|
||||||
|
|
||||||
## Implicit Requirements
|
|
||||||
1. [Implied requirement 1] - [Reasoning]
|
|
||||||
2. [Implied requirement 2] - [Reasoning]
|
|
||||||
|
|
||||||
## Success Criteria
|
|
||||||
- **Visual**: [What should it look like?]
|
|
||||||
- **Functional**: [How should it behave?]
|
|
||||||
- **Technical**: [What technical standards?]
|
|
||||||
|
|
||||||
## Assumptions
|
|
||||||
1. [Assumption 1]
|
|
||||||
2. [Assumption 2]
|
|
||||||
|
|
||||||
## Questions for User
|
|
||||||
1. [Question 1]
|
|
||||||
2. [Question 2]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Analysis Validation Checklist
|
|
||||||
```markdown
|
|
||||||
# Analysis Validation: [Task Name]
|
|
||||||
|
|
||||||
## Completeness Check
|
|
||||||
- [ ] All requirements identified
|
|
||||||
- [ ] All components analyzed
|
|
||||||
- [ ] All gaps identified
|
|
||||||
- [ ] All risks assessed
|
|
||||||
|
|
||||||
## Accuracy Check
|
|
||||||
- [ ] Requirements correctly understood
|
|
||||||
- [ ] Analysis is accurate
|
|
||||||
- [ ] Gaps are real
|
|
||||||
- [ ] Risks are valid
|
|
||||||
|
|
||||||
## Feasibility Check
|
|
||||||
- [ ] Requirements are achievable
|
|
||||||
- [ ] Timeline is realistic
|
|
||||||
- [ ] Resources are available
|
|
||||||
- [ ] Constraints are manageable
|
|
||||||
|
|
||||||
## Stakeholder Check
|
|
||||||
- [ ] User expectations aligned
|
|
||||||
- [ ] Business requirements met
|
|
||||||
- [ ] Technical constraints considered
|
|
||||||
- [ ] Quality standards maintained
|
|
||||||
```
|
|
||||||
|
|
||||||
This protocol ensures that every implementation starts with a complete understanding of what needs to be done, preventing the issues that occurred with the Social Ads Generator initial implementation.
|
|
||||||
@ -1,824 +0,0 @@
|
|||||||
# Quality Assurance Enhancement
|
|
||||||
|
|
||||||
Comprehensive tools and processes to ensure high-quality template implementations.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This document provides automated validation tools, manual review processes, and quality control measures to prevent implementation failures and ensure consistent, high-quality results.
|
|
||||||
|
|
||||||
## Automated Validation Tools
|
|
||||||
|
|
||||||
### 1. Template Structure Validator
|
|
||||||
|
|
||||||
**Purpose**: Automatically validate template structure consistency and completeness.
|
|
||||||
|
|
||||||
**Validation Script:**
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
# Template Structure Validator
|
|
||||||
# Usage: ./validate_template.sh <template_file>
|
|
||||||
|
|
||||||
TEMPLATE_FILE="$1"
|
|
||||||
VALIDATION_REPORT="validation_report_$(date +%Y%m%d_%H%M%S).md"
|
|
||||||
|
|
||||||
echo "# Template Structure Validation Report" > "$VALIDATION_REPORT"
|
|
||||||
echo "**Template**: $TEMPLATE_FILE" >> "$VALIDATION_REPORT"
|
|
||||||
echo "**Date**: $(date)" >> "$VALIDATION_REPORT"
|
|
||||||
echo "**Validator**: Template Structure Validator v1.0" >> "$VALIDATION_REPORT"
|
|
||||||
echo "" >> "$VALIDATION_REPORT"
|
|
||||||
|
|
||||||
# Check Django template structure
|
|
||||||
echo "## Django Template Structure" >> "$VALIDATION_REPORT"
|
|
||||||
echo "### Base Template Extension" >> "$VALIDATION_REPORT"
|
|
||||||
if grep -q "{% extends 'base.html' %}" "$TEMPLATE_FILE"; then
|
|
||||||
echo "✅ Base template extension found" >> "$VALIDATION_REPORT"
|
|
||||||
else
|
|
||||||
echo "❌ Base template extension missing" >> "$VALIDATION_REPORT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep -q "{% load static %}" "$TEMPLATE_FILE"; then
|
|
||||||
echo "✅ Static files loading found" >> "$VALIDATION_REPORT"
|
|
||||||
else
|
|
||||||
echo "❌ Static files loading missing" >> "$VALIDATION_REPORT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check required blocks
|
|
||||||
echo "### Required Blocks" >> "$VALIDATION_REPORT"
|
|
||||||
required_blocks=("title" "extra_css" "content" "extra_js")
|
|
||||||
for block in "${required_blocks[@]}"; do
|
|
||||||
if grep -q "{% block $block %}" "$TEMPLATE_FILE"; then
|
|
||||||
echo "✅ Block '$block' found" >> "$VALIDATION_REPORT"
|
|
||||||
else
|
|
||||||
echo "❌ Block '$block' missing" >> "$VALIDATION_REPORT"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Check HTML structure
|
|
||||||
echo "### HTML Structure" >> "$VALIDATION_REPORT"
|
|
||||||
required_elements=("agent-container" "agent-header" "agent-grid" "agent-widget")
|
|
||||||
for element in "${required_elements[@]}"; do
|
|
||||||
if grep -q "class=\"[^\"]*$element[^\"]*\"" "$TEMPLATE_FILE"; then
|
|
||||||
echo "✅ Element '$element' found" >> "$VALIDATION_REPORT"
|
|
||||||
else
|
|
||||||
echo "❌ Element '$element' missing" >> "$VALIDATION_REPORT"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Check CSS custom properties
|
|
||||||
echo "### CSS Custom Properties" >> "$VALIDATION_REPORT"
|
|
||||||
css_vars=("--primary" "--surface" "--spacing-lg" "--radius-md" "--shadow-sm")
|
|
||||||
for var in "${css_vars[@]}"; do
|
|
||||||
if grep -q "$var" "$TEMPLATE_FILE"; then
|
|
||||||
echo "✅ CSS variable '$var' found" >> "$VALIDATION_REPORT"
|
|
||||||
else
|
|
||||||
echo "❌ CSS variable '$var' missing" >> "$VALIDATION_REPORT"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Check JavaScript functions
|
|
||||||
echo "### JavaScript Functions" >> "$VALIDATION_REPORT"
|
|
||||||
js_functions=("updateWalletBalance" "showToast" "copyToClipboard" "downloadAsFile")
|
|
||||||
for func in "${js_functions[@]}"; do
|
|
||||||
if grep -q "$func" "$TEMPLATE_FILE"; then
|
|
||||||
echo "✅ JavaScript function '$func' found" >> "$VALIDATION_REPORT"
|
|
||||||
else
|
|
||||||
echo "❌ JavaScript function '$func' missing" >> "$VALIDATION_REPORT"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Check security measures
|
|
||||||
echo "### Security Measures" >> "$VALIDATION_REPORT"
|
|
||||||
if grep -q "{% csrf_token %}" "$TEMPLATE_FILE"; then
|
|
||||||
echo "✅ CSRF token found" >> "$VALIDATION_REPORT"
|
|
||||||
else
|
|
||||||
echo "❌ CSRF token missing" >> "$VALIDATION_REPORT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep -q "safeSetHTML\|HTMLSanitizer" "$TEMPLATE_FILE"; then
|
|
||||||
echo "✅ HTML sanitization found" >> "$VALIDATION_REPORT"
|
|
||||||
else
|
|
||||||
echo "❌ HTML sanitization missing" >> "$VALIDATION_REPORT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check accessibility
|
|
||||||
echo "### Accessibility" >> "$VALIDATION_REPORT"
|
|
||||||
if grep -q "aria-" "$TEMPLATE_FILE"; then
|
|
||||||
echo "✅ ARIA attributes found" >> "$VALIDATION_REPORT"
|
|
||||||
else
|
|
||||||
echo "❌ ARIA attributes missing" >> "$VALIDATION_REPORT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep -q "role=" "$TEMPLATE_FILE"; then
|
|
||||||
echo "✅ Role attributes found" >> "$VALIDATION_REPORT"
|
|
||||||
else
|
|
||||||
echo "❌ Role attributes missing" >> "$VALIDATION_REPORT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Generate summary
|
|
||||||
echo "## Validation Summary" >> "$VALIDATION_REPORT"
|
|
||||||
passed=$(grep -c "✅" "$VALIDATION_REPORT")
|
|
||||||
failed=$(grep -c "❌" "$VALIDATION_REPORT")
|
|
||||||
total=$((passed + failed))
|
|
||||||
|
|
||||||
echo "**Total Checks**: $total" >> "$VALIDATION_REPORT"
|
|
||||||
echo "**Passed**: $passed" >> "$VALIDATION_REPORT"
|
|
||||||
echo "**Failed**: $failed" >> "$VALIDATION_REPORT"
|
|
||||||
echo "**Success Rate**: $(( passed * 100 / total ))%" >> "$VALIDATION_REPORT"
|
|
||||||
|
|
||||||
if [ $failed -eq 0 ]; then
|
|
||||||
echo "**Overall Status**: ✅ PASS" >> "$VALIDATION_REPORT"
|
|
||||||
else
|
|
||||||
echo "**Overall Status**: ❌ FAIL" >> "$VALIDATION_REPORT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Validation report generated: $VALIDATION_REPORT"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. CSS Class Auditor
|
|
||||||
|
|
||||||
**Purpose**: Verify CSS class usage and consistency across templates.
|
|
||||||
|
|
||||||
**Auditor Script:**
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
# CSS Class Auditor
|
|
||||||
# Usage: ./audit_css_classes.sh <template_file> <reference_file>
|
|
||||||
|
|
||||||
TEMPLATE_FILE="$1"
|
|
||||||
REFERENCE_FILE="$2"
|
|
||||||
AUDIT_REPORT="css_audit_$(date +%Y%m%d_%H%M%S).md"
|
|
||||||
|
|
||||||
echo "# CSS Class Audit Report" > "$AUDIT_REPORT"
|
|
||||||
echo "**Template**: $TEMPLATE_FILE" >> "$AUDIT_REPORT"
|
|
||||||
echo "**Reference**: $REFERENCE_FILE" >> "$AUDIT_REPORT"
|
|
||||||
echo "**Date**: $(date)" >> "$AUDIT_REPORT"
|
|
||||||
echo "" >> "$AUDIT_REPORT"
|
|
||||||
|
|
||||||
# Extract CSS classes from both files
|
|
||||||
echo "## CSS Class Analysis" >> "$AUDIT_REPORT"
|
|
||||||
|
|
||||||
# Get classes from template
|
|
||||||
grep -oE 'class="[^"]*"' "$TEMPLATE_FILE" | sed 's/class="//g' | sed 's/"//g' | tr ' ' '\n' | sort | uniq > template_classes.tmp
|
|
||||||
|
|
||||||
# Get classes from reference
|
|
||||||
grep -oE 'class="[^"]*"' "$REFERENCE_FILE" | sed 's/class="//g' | sed 's/"//g' | tr ' ' '\n' | sort | uniq > reference_classes.tmp
|
|
||||||
|
|
||||||
# Compare classes
|
|
||||||
echo "### Classes in Reference but Missing in Template" >> "$AUDIT_REPORT"
|
|
||||||
comm -23 reference_classes.tmp template_classes.tmp | while read class; do
|
|
||||||
echo "❌ Missing class: \`$class\`" >> "$AUDIT_REPORT"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "### Classes in Template but Not in Reference" >> "$AUDIT_REPORT"
|
|
||||||
comm -13 reference_classes.tmp template_classes.tmp | while read class; do
|
|
||||||
echo "⚠️ Extra class: \`$class\`" >> "$AUDIT_REPORT"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "### Common Classes" >> "$AUDIT_REPORT"
|
|
||||||
comm -12 reference_classes.tmp template_classes.tmp | while read class; do
|
|
||||||
echo "✅ Common class: \`$class\`" >> "$AUDIT_REPORT"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
rm template_classes.tmp reference_classes.tmp
|
|
||||||
|
|
||||||
echo "CSS class audit report generated: $AUDIT_REPORT"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. JavaScript Function Checker
|
|
||||||
|
|
||||||
**Purpose**: Verify JavaScript function equivalence and behavior.
|
|
||||||
|
|
||||||
**Checker Script:**
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
# JavaScript Function Checker
|
|
||||||
# Usage: ./check_js_functions.sh <template_file> <reference_file>
|
|
||||||
|
|
||||||
TEMPLATE_FILE="$1"
|
|
||||||
REFERENCE_FILE="$2"
|
|
||||||
JS_REPORT="js_function_report_$(date +%Y%m%d_%H%M%S).md"
|
|
||||||
|
|
||||||
echo "# JavaScript Function Analysis Report" > "$JS_REPORT"
|
|
||||||
echo "**Template**: $TEMPLATE_FILE" >> "$JS_REPORT"
|
|
||||||
echo "**Reference**: $REFERENCE_FILE" >> "$JS_REPORT"
|
|
||||||
echo "**Date**: $(date)" >> "$JS_REPORT"
|
|
||||||
echo "" >> "$JS_REPORT"
|
|
||||||
|
|
||||||
# Extract function names
|
|
||||||
echo "## Function Analysis" >> "$JS_REPORT"
|
|
||||||
|
|
||||||
# Get functions from template
|
|
||||||
grep -oE 'function [a-zA-Z_][a-zA-Z0-9_]*' "$TEMPLATE_FILE" | sed 's/function //g' | sort | uniq > template_functions.tmp
|
|
||||||
|
|
||||||
# Get functions from reference
|
|
||||||
grep -oE 'function [a-zA-Z_][a-zA-Z0-9_]*' "$REFERENCE_FILE" | sed 's/function //g' | sort | uniq > reference_functions.tmp
|
|
||||||
|
|
||||||
# Compare functions
|
|
||||||
echo "### Functions in Reference but Missing in Template" >> "$JS_REPORT"
|
|
||||||
comm -23 reference_functions.tmp template_functions.tmp | while read func; do
|
|
||||||
echo "❌ Missing function: \`$func\`" >> "$JS_REPORT"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "### Functions in Template but Not in Reference" >> "$JS_REPORT"
|
|
||||||
comm -13 reference_functions.tmp template_functions.tmp | while read func; do
|
|
||||||
echo "⚠️ Extra function: \`$func\`" >> "$JS_REPORT"
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "### Common Functions" >> "$JS_REPORT"
|
|
||||||
comm -12 reference_functions.tmp template_functions.tmp | while read func; do
|
|
||||||
echo "✅ Common function: \`$func\`" >> "$JS_REPORT"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Check for essential functions
|
|
||||||
echo "### Essential Function Check" >> "$JS_REPORT"
|
|
||||||
essential_functions=("updateWalletBalance" "showToast" "copyToClipboard" "downloadAsFile" "resetUI")
|
|
||||||
for func in "${essential_functions[@]}"; do
|
|
||||||
if grep -q "$func" "$TEMPLATE_FILE"; then
|
|
||||||
echo "✅ Essential function '$func' found" >> "$JS_REPORT"
|
|
||||||
else
|
|
||||||
echo "❌ Essential function '$func' missing" >> "$JS_REPORT"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
rm template_functions.tmp reference_functions.tmp
|
|
||||||
|
|
||||||
echo "JavaScript function analysis report generated: $JS_REPORT"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Accessibility Compliance Checker
|
|
||||||
|
|
||||||
**Purpose**: Validate accessibility standards and ARIA attributes.
|
|
||||||
|
|
||||||
**Accessibility Checker:**
|
|
||||||
```bash
|
|
||||||
#!/bin/bash
|
|
||||||
# Accessibility Compliance Checker
|
|
||||||
# Usage: ./check_accessibility.sh <template_file>
|
|
||||||
|
|
||||||
TEMPLATE_FILE="$1"
|
|
||||||
A11Y_REPORT="accessibility_report_$(date +%Y%m%d_%H%M%S).md"
|
|
||||||
|
|
||||||
echo "# Accessibility Compliance Report" > "$A11Y_REPORT"
|
|
||||||
echo "**Template**: $TEMPLATE_FILE" >> "$A11Y_REPORT"
|
|
||||||
echo "**Date**: $(date)" >> "$A11Y_REPORT"
|
|
||||||
echo "" >> "$A11Y_REPORT"
|
|
||||||
|
|
||||||
# Check ARIA attributes
|
|
||||||
echo "## ARIA Attributes" >> "$A11Y_REPORT"
|
|
||||||
aria_attributes=("aria-label" "aria-labelledby" "aria-describedby" "aria-expanded" "aria-hidden" "aria-live" "role")
|
|
||||||
for attr in "${aria_attributes[@]}"; do
|
|
||||||
if grep -q "$attr=" "$TEMPLATE_FILE"; then
|
|
||||||
count=$(grep -c "$attr=" "$TEMPLATE_FILE")
|
|
||||||
echo "✅ $attr found ($count occurrences)" >> "$A11Y_REPORT"
|
|
||||||
else
|
|
||||||
echo "❌ $attr missing" >> "$A11Y_REPORT"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Check form accessibility
|
|
||||||
echo "## Form Accessibility" >> "$A11Y_REPORT"
|
|
||||||
if grep -q "<label" "$TEMPLATE_FILE"; then
|
|
||||||
echo "✅ Form labels found" >> "$A11Y_REPORT"
|
|
||||||
else
|
|
||||||
echo "❌ Form labels missing" >> "$A11Y_REPORT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if grep -q "for=" "$TEMPLATE_FILE"; then
|
|
||||||
echo "✅ Label associations found" >> "$A11Y_REPORT"
|
|
||||||
else
|
|
||||||
echo "❌ Label associations missing" >> "$A11Y_REPORT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check heading structure
|
|
||||||
echo "## Heading Structure" >> "$A11Y_REPORT"
|
|
||||||
for i in {1..6}; do
|
|
||||||
if grep -q "<h$i" "$TEMPLATE_FILE"; then
|
|
||||||
count=$(grep -c "<h$i" "$TEMPLATE_FILE")
|
|
||||||
echo "✅ H$i headings found ($count occurrences)" >> "$A11Y_REPORT"
|
|
||||||
else
|
|
||||||
echo "ℹ️ H$i headings not found" >> "$A11Y_REPORT"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Check alt text for images
|
|
||||||
echo "## Image Accessibility" >> "$A11Y_REPORT"
|
|
||||||
if grep -q "<img" "$TEMPLATE_FILE"; then
|
|
||||||
if grep -q "alt=" "$TEMPLATE_FILE"; then
|
|
||||||
echo "✅ Image alt text found" >> "$A11Y_REPORT"
|
|
||||||
else
|
|
||||||
echo "❌ Image alt text missing" >> "$A11Y_REPORT"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "ℹ️ No images found" >> "$A11Y_REPORT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check focus management
|
|
||||||
echo "## Focus Management" >> "$A11Y_REPORT"
|
|
||||||
if grep -q "focus()" "$TEMPLATE_FILE"; then
|
|
||||||
echo "✅ Focus management found" >> "$A11Y_REPORT"
|
|
||||||
else
|
|
||||||
echo "❌ Focus management missing" >> "$A11Y_REPORT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Check keyboard navigation
|
|
||||||
echo "## Keyboard Navigation" >> "$A11Y_REPORT"
|
|
||||||
if grep -q "keydown\|keyup\|keypress" "$TEMPLATE_FILE"; then
|
|
||||||
echo "✅ Keyboard event handling found" >> "$A11Y_REPORT"
|
|
||||||
else
|
|
||||||
echo "❌ Keyboard event handling missing" >> "$A11Y_REPORT"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Accessibility compliance report generated: $A11Y_REPORT"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Manual Review Processes
|
|
||||||
|
|
||||||
### 1. Pixel-Perfect Visual Comparison
|
|
||||||
|
|
||||||
**Visual Comparison Checklist:**
|
|
||||||
```markdown
|
|
||||||
# Visual Comparison Checklist
|
|
||||||
|
|
||||||
## Layout Structure
|
|
||||||
- [ ] Header layout matches exactly
|
|
||||||
- [ ] Main content area positioning correct
|
|
||||||
- [ ] Sidebar placement accurate
|
|
||||||
- [ ] Footer alignment proper
|
|
||||||
- [ ] Overall grid structure identical
|
|
||||||
|
|
||||||
## Typography
|
|
||||||
- [ ] Font families match
|
|
||||||
- [ ] Font sizes identical
|
|
||||||
- [ ] Font weights correct
|
|
||||||
- [ ] Line heights consistent
|
|
||||||
- [ ] Letter spacing accurate
|
|
||||||
|
|
||||||
## Color Scheme
|
|
||||||
- [ ] Primary colors match
|
|
||||||
- [ ] Secondary colors accurate
|
|
||||||
- [ ] Background colors correct
|
|
||||||
- [ ] Text colors identical
|
|
||||||
- [ ] Accent colors consistent
|
|
||||||
|
|
||||||
## Spacing and Padding
|
|
||||||
- [ ] Margin values match
|
|
||||||
- [ ] Padding values identical
|
|
||||||
- [ ] Gap sizes consistent
|
|
||||||
- [ ] Border spacing accurate
|
|
||||||
- [ ] Element spacing proper
|
|
||||||
|
|
||||||
## Visual Effects
|
|
||||||
- [ ] Shadows match exactly
|
|
||||||
- [ ] Border radius identical
|
|
||||||
- [ ] Gradients accurate
|
|
||||||
- [ ] Hover effects consistent
|
|
||||||
- [ ] Transition timing correct
|
|
||||||
|
|
||||||
## Responsive Design
|
|
||||||
- [ ] Mobile layout matches
|
|
||||||
- [ ] Tablet layout accurate
|
|
||||||
- [ ] Desktop layout identical
|
|
||||||
- [ ] Breakpoint behavior consistent
|
|
||||||
- [ ] Scaling behavior proper
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Interaction Testing Protocol
|
|
||||||
|
|
||||||
**Interaction Testing Checklist:**
|
|
||||||
```markdown
|
|
||||||
# Interaction Testing Checklist
|
|
||||||
|
|
||||||
## Click Interactions
|
|
||||||
- [ ] All buttons respond to clicks
|
|
||||||
- [ ] Click areas are appropriate size
|
|
||||||
- [ ] Click feedback is immediate
|
|
||||||
- [ ] Double-click prevention works
|
|
||||||
- [ ] Context menus work correctly
|
|
||||||
|
|
||||||
## Form Interactions
|
|
||||||
- [ ] Input fields accept text
|
|
||||||
- [ ] Validation works correctly
|
|
||||||
- [ ] Submit buttons function
|
|
||||||
- [ ] Reset buttons clear forms
|
|
||||||
- [ ] Error messages display properly
|
|
||||||
|
|
||||||
## Navigation Interactions
|
|
||||||
- [ ] Menu items work correctly
|
|
||||||
- [ ] Breadcrumbs function properly
|
|
||||||
- [ ] Back/forward buttons work
|
|
||||||
- [ ] Internal links navigate correctly
|
|
||||||
- [ ] External links open properly
|
|
||||||
|
|
||||||
## Keyboard Navigation
|
|
||||||
- [ ] Tab order is logical
|
|
||||||
- [ ] Enter key activates buttons
|
|
||||||
- [ ] Escape key closes modals
|
|
||||||
- [ ] Arrow keys work for navigation
|
|
||||||
- [ ] Shortcuts function correctly
|
|
||||||
|
|
||||||
## Mouse Interactions
|
|
||||||
- [ ] Hover effects work
|
|
||||||
- [ ] Click states are visual
|
|
||||||
- [ ] Drag and drop functions
|
|
||||||
- [ ] Scroll behaviors work
|
|
||||||
- [ ] Context menus appear
|
|
||||||
|
|
||||||
## Touch Interactions
|
|
||||||
- [ ] Tap targets are large enough
|
|
||||||
- [ ] Swipe gestures work
|
|
||||||
- [ ] Pinch zoom functions
|
|
||||||
- [ ] Touch feedback is immediate
|
|
||||||
- [ ] Touch scrolling is smooth
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Performance Validation Process
|
|
||||||
|
|
||||||
**Performance Validation Checklist:**
|
|
||||||
```markdown
|
|
||||||
# Performance Validation Checklist
|
|
||||||
|
|
||||||
## Loading Performance
|
|
||||||
- [ ] Page load time under 3 seconds
|
|
||||||
- [ ] First contentful paint under 1.5 seconds
|
|
||||||
- [ ] Time to interactive under 2.5 seconds
|
|
||||||
- [ ] Cumulative layout shift under 0.1
|
|
||||||
- [ ] Resource loading optimized
|
|
||||||
|
|
||||||
## Runtime Performance
|
|
||||||
- [ ] Smooth scrolling (60fps)
|
|
||||||
- [ ] Animations run smoothly
|
|
||||||
- [ ] No memory leaks detected
|
|
||||||
- [ ] CPU usage reasonable
|
|
||||||
- [ ] Network requests optimized
|
|
||||||
|
|
||||||
## Resource Usage
|
|
||||||
- [ ] Image sizes optimized
|
|
||||||
- [ ] CSS file size reasonable
|
|
||||||
- [ ] JavaScript file size optimized
|
|
||||||
- [ ] Font loading efficient
|
|
||||||
- [ ] Third-party resources minimal
|
|
||||||
|
|
||||||
## Caching Performance
|
|
||||||
- [ ] Browser caching configured
|
|
||||||
- [ ] CDN caching working
|
|
||||||
- [ ] API response caching
|
|
||||||
- [ ] Static asset caching
|
|
||||||
- [ ] Database query optimization
|
|
||||||
|
|
||||||
## Mobile Performance
|
|
||||||
- [ ] Mobile load time acceptable
|
|
||||||
- [ ] Touch response immediate
|
|
||||||
- [ ] Scroll performance smooth
|
|
||||||
- [ ] Battery usage reasonable
|
|
||||||
- [ ] Data usage optimized
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Cross-Browser Compatibility Testing
|
|
||||||
|
|
||||||
**Browser Compatibility Matrix:**
|
|
||||||
```markdown
|
|
||||||
# Browser Compatibility Testing Matrix
|
|
||||||
|
|
||||||
## Desktop Browsers
|
|
||||||
| Feature | Chrome | Firefox | Safari | Edge | Status |
|
|
||||||
|---------|---------|---------|---------|---------|---------|
|
|
||||||
| Layout | [ ] | [ ] | [ ] | [ ] | [ ] |
|
|
||||||
| Styling | [ ] | [ ] | [ ] | [ ] | [ ] |
|
|
||||||
| JavaScript | [ ] | [ ] | [ ] | [ ] | [ ] |
|
|
||||||
| Interactions | [ ] | [ ] | [ ] | [ ] | [ ] |
|
|
||||||
| Performance | [ ] | [ ] | [ ] | [ ] | [ ] |
|
|
||||||
|
|
||||||
## Mobile Browsers
|
|
||||||
| Feature | Chrome Mobile | Safari Mobile | Firefox Mobile | Samsung Browser | Status |
|
|
||||||
|---------|---------|---------|---------|---------|---------|
|
|
||||||
| Layout | [ ] | [ ] | [ ] | [ ] | [ ] |
|
|
||||||
| Styling | [ ] | [ ] | [ ] | [ ] | [ ] |
|
|
||||||
| JavaScript | [ ] | [ ] | [ ] | [ ] | [ ] |
|
|
||||||
| Interactions | [ ] | [ ] | [ ] | [ ] | [ ] |
|
|
||||||
| Performance | [ ] | [ ] | [ ] | [ ] | [ ] |
|
|
||||||
|
|
||||||
## Compatibility Issues
|
|
||||||
- [ ] CSS Grid support verified
|
|
||||||
- [ ] Flexbox compatibility confirmed
|
|
||||||
- [ ] ES6 features working
|
|
||||||
- [ ] CSS Custom Properties supported
|
|
||||||
- [ ] Modern JavaScript APIs available
|
|
||||||
|
|
||||||
## Fallback Mechanisms
|
|
||||||
- [ ] Graceful degradation implemented
|
|
||||||
- [ ] Progressive enhancement working
|
|
||||||
- [ ] Polyfills loaded when needed
|
|
||||||
- [ ] Fallback styles provided
|
|
||||||
- [ ] Error handling for unsupported features
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quality Control Measures
|
|
||||||
|
|
||||||
### 1. Code Quality Standards
|
|
||||||
|
|
||||||
**Code Quality Checklist:**
|
|
||||||
```markdown
|
|
||||||
# Code Quality Standards Checklist
|
|
||||||
|
|
||||||
## HTML Quality
|
|
||||||
- [ ] Valid HTML5 markup
|
|
||||||
- [ ] Semantic HTML elements used
|
|
||||||
- [ ] Proper nesting structure
|
|
||||||
- [ ] Accessibility attributes included
|
|
||||||
- [ ] SEO meta tags present
|
|
||||||
|
|
||||||
## CSS Quality
|
|
||||||
- [ ] Valid CSS3 syntax
|
|
||||||
- [ ] Consistent naming conventions
|
|
||||||
- [ ] Modular CSS architecture
|
|
||||||
- [ ] Responsive design principles
|
|
||||||
- [ ] Performance optimizations
|
|
||||||
|
|
||||||
## JavaScript Quality
|
|
||||||
- [ ] Valid JavaScript syntax
|
|
||||||
- [ ] Consistent coding style
|
|
||||||
- [ ] Proper error handling
|
|
||||||
- [ ] Memory leak prevention
|
|
||||||
- [ ] Security best practices
|
|
||||||
|
|
||||||
## Django Template Quality
|
|
||||||
- [ ] Proper template inheritance
|
|
||||||
- [ ] Correct template tag usage
|
|
||||||
- [ ] CSRF protection implemented
|
|
||||||
- [ ] XSS prevention measures
|
|
||||||
- [ ] Template variable escaping
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Security Validation
|
|
||||||
|
|
||||||
**Security Validation Checklist:**
|
|
||||||
```markdown
|
|
||||||
# Security Validation Checklist
|
|
||||||
|
|
||||||
## Input Validation
|
|
||||||
- [ ] All user inputs validated
|
|
||||||
- [ ] XSS prevention implemented
|
|
||||||
- [ ] SQL injection prevention
|
|
||||||
- [ ] File upload security
|
|
||||||
- [ ] Input sanitization active
|
|
||||||
|
|
||||||
## Output Sanitization
|
|
||||||
- [ ] HTML output sanitized
|
|
||||||
- [ ] JavaScript output escaped
|
|
||||||
- [ ] CSS output cleaned
|
|
||||||
- [ ] JSON output validated
|
|
||||||
- [ ] XML output sanitized
|
|
||||||
|
|
||||||
## Authentication Security
|
|
||||||
- [ ] CSRF tokens present
|
|
||||||
- [ ] Session management secure
|
|
||||||
- [ ] Password security enforced
|
|
||||||
- [ ] Access control implemented
|
|
||||||
- [ ] Rate limiting active
|
|
||||||
|
|
||||||
## Data Protection
|
|
||||||
- [ ] Sensitive data encrypted
|
|
||||||
- [ ] Secure data transmission
|
|
||||||
- [ ] Data validation implemented
|
|
||||||
- [ ] Error message sanitization
|
|
||||||
- [ ] Logging security measures
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Performance Benchmarks
|
|
||||||
|
|
||||||
**Performance Benchmark Standards:**
|
|
||||||
```markdown
|
|
||||||
# Performance Benchmark Standards
|
|
||||||
|
|
||||||
## Loading Performance Targets
|
|
||||||
- **Page Load Time**: < 3 seconds
|
|
||||||
- **First Contentful Paint**: < 1.5 seconds
|
|
||||||
- **Time to Interactive**: < 2.5 seconds
|
|
||||||
- **Cumulative Layout Shift**: < 0.1
|
|
||||||
- **First Input Delay**: < 100ms
|
|
||||||
|
|
||||||
## Runtime Performance Targets
|
|
||||||
- **Animation Frame Rate**: 60fps
|
|
||||||
- **Scroll Performance**: Smooth scrolling
|
|
||||||
- **Memory Usage**: < 50MB
|
|
||||||
- **CPU Usage**: < 20%
|
|
||||||
- **Network Requests**: < 10 initial requests
|
|
||||||
|
|
||||||
## Resource Size Targets
|
|
||||||
- **HTML Size**: < 50KB
|
|
||||||
- **CSS Size**: < 100KB
|
|
||||||
- **JavaScript Size**: < 200KB
|
|
||||||
- **Image Total Size**: < 1MB
|
|
||||||
- **Total Page Size**: < 2MB
|
|
||||||
|
|
||||||
## Mobile Performance Targets
|
|
||||||
- **Mobile Load Time**: < 4 seconds
|
|
||||||
- **Touch Response**: < 50ms
|
|
||||||
- **Battery Usage**: Minimal impact
|
|
||||||
- **Data Usage**: < 1MB initial load
|
|
||||||
- **Offline Capability**: Basic functionality
|
|
||||||
```
|
|
||||||
|
|
||||||
## Automated Testing Integration
|
|
||||||
|
|
||||||
### 1. Continuous Integration Pipeline
|
|
||||||
|
|
||||||
**CI/CD Pipeline Configuration:**
|
|
||||||
```yaml
|
|
||||||
# .github/workflows/template-validation.yml
|
|
||||||
name: Template Validation
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- '*/templates/**/*.html'
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- '*/templates/**/*.html'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
validate-templates:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: '3.9'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
pip install beautifulsoup4 lxml
|
|
||||||
npm install -g html-validate
|
|
||||||
|
|
||||||
- name: Validate HTML structure
|
|
||||||
run: |
|
|
||||||
for file in $(find . -name "*.html" -path "*/templates/*"); do
|
|
||||||
echo "Validating $file"
|
|
||||||
html-validate "$file"
|
|
||||||
done
|
|
||||||
|
|
||||||
- name: Check template structure
|
|
||||||
run: |
|
|
||||||
python scripts/validate_template_structure.py
|
|
||||||
|
|
||||||
- name: Run accessibility tests
|
|
||||||
run: |
|
|
||||||
python scripts/check_accessibility.py
|
|
||||||
|
|
||||||
- name: Generate validation report
|
|
||||||
run: |
|
|
||||||
python scripts/generate_validation_report.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Automated Testing Scripts
|
|
||||||
|
|
||||||
**Template Validation Script:**
|
|
||||||
```python
|
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Template Structure Validation Script
|
|
||||||
Validates Django templates for structural consistency
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
|
|
||||||
class TemplateValidator:
|
|
||||||
def __init__(self):
|
|
||||||
self.errors = []
|
|
||||||
self.warnings = []
|
|
||||||
|
|
||||||
def validate_template(self, template_path):
|
|
||||||
"""Validate a single template file"""
|
|
||||||
with open(template_path, 'r', encoding='utf-8') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
# Check Django template structure
|
|
||||||
self._check_django_structure(content, template_path)
|
|
||||||
|
|
||||||
# Check HTML structure
|
|
||||||
self._check_html_structure(content, template_path)
|
|
||||||
|
|
||||||
# Check CSS classes
|
|
||||||
self._check_css_classes(content, template_path)
|
|
||||||
|
|
||||||
# Check JavaScript functions
|
|
||||||
self._check_javascript_functions(content, template_path)
|
|
||||||
|
|
||||||
# Check accessibility
|
|
||||||
self._check_accessibility(content, template_path)
|
|
||||||
|
|
||||||
# Check security
|
|
||||||
self._check_security(content, template_path)
|
|
||||||
|
|
||||||
def _check_django_structure(self, content, template_path):
|
|
||||||
"""Check Django template structure"""
|
|
||||||
if "{% extends 'base.html' %}" not in content:
|
|
||||||
self.errors.append(f"{template_path}: Missing base template extension")
|
|
||||||
|
|
||||||
if "{% load static %}" not in content:
|
|
||||||
self.errors.append(f"{template_path}: Missing static files loading")
|
|
||||||
|
|
||||||
required_blocks = ['title', 'extra_css', 'content', 'extra_js']
|
|
||||||
for block in required_blocks:
|
|
||||||
if f"{{% block {block} %}}" not in content:
|
|
||||||
self.errors.append(f"{template_path}: Missing {block} block")
|
|
||||||
|
|
||||||
def _check_html_structure(self, content, template_path):
|
|
||||||
"""Check HTML structure"""
|
|
||||||
required_classes = ['agent-container', 'agent-header', 'agent-grid']
|
|
||||||
for class_name in required_classes:
|
|
||||||
if f'class="{class_name}"' not in content and f'class="[^"]*{class_name}[^"]*"' not in content:
|
|
||||||
self.errors.append(f"{template_path}: Missing {class_name} class")
|
|
||||||
|
|
||||||
def _check_css_classes(self, content, template_path):
|
|
||||||
"""Check CSS class usage"""
|
|
||||||
# Extract all CSS classes
|
|
||||||
css_classes = re.findall(r'class="([^"]*)"', content)
|
|
||||||
|
|
||||||
# Check for standard classes
|
|
||||||
standard_classes = ['btn', 'form-control', 'agent-widget']
|
|
||||||
for class_name in standard_classes:
|
|
||||||
if not any(class_name in class_list for class_list in css_classes):
|
|
||||||
self.warnings.append(f"{template_path}: Standard class {class_name} not found")
|
|
||||||
|
|
||||||
def _check_javascript_functions(self, content, template_path):
|
|
||||||
"""Check JavaScript functions"""
|
|
||||||
required_functions = ['updateWalletBalance', 'showToast']
|
|
||||||
for func in required_functions:
|
|
||||||
if func not in content:
|
|
||||||
self.errors.append(f"{template_path}: Missing {func} function")
|
|
||||||
|
|
||||||
def _check_accessibility(self, content, template_path):
|
|
||||||
"""Check accessibility features"""
|
|
||||||
if 'aria-' not in content:
|
|
||||||
self.warnings.append(f"{template_path}: No ARIA attributes found")
|
|
||||||
|
|
||||||
if 'role=' not in content:
|
|
||||||
self.warnings.append(f"{template_path}: No role attributes found")
|
|
||||||
|
|
||||||
def _check_security(self, content, template_path):
|
|
||||||
"""Check security measures"""
|
|
||||||
if '{% csrf_token %}' not in content:
|
|
||||||
self.errors.append(f"{template_path}: Missing CSRF token")
|
|
||||||
|
|
||||||
if 'safeSetHTML' not in content and 'HTMLSanitizer' not in content:
|
|
||||||
self.warnings.append(f"{template_path}: No HTML sanitization found")
|
|
||||||
|
|
||||||
def generate_report(self):
|
|
||||||
"""Generate validation report"""
|
|
||||||
report = f"""# Template Validation Report
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
- **Total Errors**: {len(self.errors)}
|
|
||||||
- **Total Warnings**: {len(self.warnings)}
|
|
||||||
|
|
||||||
## Errors
|
|
||||||
"""
|
|
||||||
for error in self.errors:
|
|
||||||
report += f"- ❌ {error}\n"
|
|
||||||
|
|
||||||
report += "\n## Warnings\n"
|
|
||||||
for warning in self.warnings:
|
|
||||||
report += f"- ⚠️ {warning}\n"
|
|
||||||
|
|
||||||
return report
|
|
||||||
|
|
||||||
def main():
|
|
||||||
validator = TemplateValidator()
|
|
||||||
|
|
||||||
# Find all template files
|
|
||||||
template_files = []
|
|
||||||
for root, dirs, files in os.walk('.'):
|
|
||||||
if 'templates' in root:
|
|
||||||
for file in files:
|
|
||||||
if file.endswith('.html'):
|
|
||||||
template_files.append(os.path.join(root, file))
|
|
||||||
|
|
||||||
# Validate each template
|
|
||||||
for template_file in template_files:
|
|
||||||
validator.validate_template(template_file)
|
|
||||||
|
|
||||||
# Generate and save report
|
|
||||||
report = validator.generate_report()
|
|
||||||
with open('template_validation_report.md', 'w') as f:
|
|
||||||
f.write(report)
|
|
||||||
|
|
||||||
print(f"Validation complete. Found {len(validator.errors)} errors and {len(validator.warnings)} warnings.")
|
|
||||||
|
|
||||||
if validator.errors:
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
```
|
|
||||||
|
|
||||||
This comprehensive Quality Assurance Enhancement framework provides automated tools, manual processes, and quality control measures to ensure high-quality template implementations and prevent the issues that occurred with the Social Ads Generator initial implementation.
|
|
||||||
@ -1,153 +0,0 @@
|
|||||||
# Railway Deployment Setup Guide
|
|
||||||
|
|
||||||
## Current Issue: SQLite Database (Ephemeral)
|
|
||||||
|
|
||||||
Your Railway deployment is currently using SQLite, which is **ephemeral** and loses all data on every deployment. This is why your users disappear.
|
|
||||||
|
|
||||||
## Solution: Add PostgreSQL Database
|
|
||||||
|
|
||||||
### Step 1: Add PostgreSQL to Railway Project
|
|
||||||
|
|
||||||
1. **Go to your Railway project dashboard**
|
|
||||||
2. **Click "New" → "Database" → "Add PostgreSQL"**
|
|
||||||
3. **Railway will automatically create a PostgreSQL database**
|
|
||||||
4. **Railway will automatically set the `DATABASE_URL` environment variable**
|
|
||||||
|
|
||||||
### Step 2: Verify Environment Variables
|
|
||||||
|
|
||||||
After adding PostgreSQL, check that these environment variables are set in Railway:
|
|
||||||
|
|
||||||
**Required Variables:**
|
|
||||||
- `DATABASE_URL` - Should be automatically set by Railway PostgreSQL addon
|
|
||||||
- `SECRET_KEY` - Set to a secure random string
|
|
||||||
- `DEBUG` - Set to `False` for production
|
|
||||||
- `ALLOWED_HOSTS` - Set to `netcop.up.railway.app,*.railway.app`
|
|
||||||
- `CSRF_TRUSTED_ORIGINS` - Set to `https://netcop.up.railway.app`
|
|
||||||
|
|
||||||
**API Keys:**
|
|
||||||
- `OPENWEATHER_API_KEY` - Your OpenWeather API key
|
|
||||||
- `STRIPE_SECRET_KEY` - Your Stripe secret key (sk_test_...)
|
|
||||||
- `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` - Your Stripe publishable key (pk_test_...)
|
|
||||||
- `STRIPE_WEBHOOK_SECRET` - Your Stripe webhook secret (optional - whsec_...)
|
|
||||||
|
|
||||||
**N8N Webhook URLs:**
|
|
||||||
- `N8N_WEBHOOK_DATA_ANALYZER` - Your N8N webhook URL
|
|
||||||
- `N8N_WEBHOOK_JOB_POSTING` - Your N8N webhook URL
|
|
||||||
- `N8N_WEBHOOK_SOCIAL_ADS` - Your N8N webhook URL
|
|
||||||
|
|
||||||
### Step 3: Deploy with PostgreSQL
|
|
||||||
|
|
||||||
Once PostgreSQL is added:
|
|
||||||
|
|
||||||
1. **Your next deployment will use PostgreSQL**
|
|
||||||
2. **The database will persist between deployments**
|
|
||||||
3. **Users and data will be preserved**
|
|
||||||
|
|
||||||
### Step 4: Create Your Admin User
|
|
||||||
|
|
||||||
After successful deployment with PostgreSQL, create your admin user:
|
|
||||||
|
|
||||||
**Option A: Use Railway Console**
|
|
||||||
```bash
|
|
||||||
# In Railway project console, run:
|
|
||||||
python manage.py create_user your-email@example.com your-password --superuser --balance 100
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option B: Use Django Admin**
|
|
||||||
```bash
|
|
||||||
# Create superuser via Railway console:
|
|
||||||
python manage.py createsuperuser
|
|
||||||
```
|
|
||||||
|
|
||||||
## Database Verification Commands
|
|
||||||
|
|
||||||
Use these commands in Railway console to check database status:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check database info and user count
|
|
||||||
python manage.py backup_users --action info
|
|
||||||
|
|
||||||
# Create a new user with wallet balance
|
|
||||||
python manage.py create_user user@example.com password123 --balance 50.00
|
|
||||||
|
|
||||||
# Create admin user
|
|
||||||
python manage.py create_user admin@yoursite.com securepassword --superuser --balance 100
|
|
||||||
```
|
|
||||||
|
|
||||||
## Environment Variables Template
|
|
||||||
|
|
||||||
Copy these to Railway environment variables:
|
|
||||||
|
|
||||||
```env
|
|
||||||
# Django Core
|
|
||||||
SECRET_KEY=your-very-long-random-secret-key-here
|
|
||||||
DEBUG=False
|
|
||||||
ALLOWED_HOSTS=netcop.up.railway.app,*.railway.app
|
|
||||||
CSRF_TRUSTED_ORIGINS=https://netcop.up.railway.app
|
|
||||||
|
|
||||||
# Database (automatically set by Railway PostgreSQL addon)
|
|
||||||
DATABASE_URL=postgresql://...
|
|
||||||
|
|
||||||
# OpenWeather API
|
|
||||||
OPENWEATHER_API_KEY=your-openweather-api-key
|
|
||||||
|
|
||||||
# Stripe (API-based payment system)
|
|
||||||
STRIPE_SECRET_KEY=sk_test_your_secret_key_here
|
|
||||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key_here
|
|
||||||
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret_here # Optional
|
|
||||||
|
|
||||||
# N8N Webhooks
|
|
||||||
N8N_WEBHOOK_DATA_ANALYZER=https://your-n8n.com/webhook/data-analyzer
|
|
||||||
N8N_WEBHOOK_JOB_POSTING=https://your-n8n.com/webhook/job-posting
|
|
||||||
N8N_WEBHOOK_SOCIAL_ADS=https://your-n8n.com/webhook/social-ads
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quick Fix Steps
|
|
||||||
|
|
||||||
1. **Add PostgreSQL database in Railway**
|
|
||||||
2. **Wait for deployment to complete**
|
|
||||||
3. **Run: `python manage.py backup_users --action info`**
|
|
||||||
4. **Create your user: `python manage.py create_user your@email.com password --superuser --balance 100`**
|
|
||||||
5. **Test login at https://netcop.up.railway.app/auth/login/**
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### If still using SQLite:
|
|
||||||
- Check that `DATABASE_URL` environment variable is set in Railway
|
|
||||||
- Restart the Railway app after adding PostgreSQL
|
|
||||||
- Check Railway logs for connection errors
|
|
||||||
|
|
||||||
### If users still disappearing:
|
|
||||||
- Verify PostgreSQL addon is active
|
|
||||||
- Check Railway database tab shows PostgreSQL (not empty)
|
|
||||||
- Run database info command to verify connection
|
|
||||||
|
|
||||||
### If login still fails:
|
|
||||||
- Check CSRF_TRUSTED_ORIGINS includes your Railway domain
|
|
||||||
- Verify ALLOWED_HOSTS includes your Railway domain
|
|
||||||
- Check browser network tab for CSRF errors
|
|
||||||
|
|
||||||
## Expected Railway Logs (After PostgreSQL)
|
|
||||||
|
|
||||||
```
|
|
||||||
=== DATABASE INFO ===
|
|
||||||
Database Engine: django.db.backends.postgresql
|
|
||||||
Database Name: railway
|
|
||||||
Total Users: X
|
|
||||||
Superusers: 1
|
|
||||||
```
|
|
||||||
|
|
||||||
**Key:** Look for `postgresql` engine, not `sqlite3`!
|
|
||||||
|
|
||||||
## Payment System Notes
|
|
||||||
|
|
||||||
The payment system uses **API-based verification** instead of webhooks:
|
|
||||||
- ✅ **More reliable** than webhook delivery
|
|
||||||
- ✅ **Instant confirmation** when users return from Stripe
|
|
||||||
- ✅ **No webhook delivery issues** on Railway
|
|
||||||
- ✅ **Simpler debugging** and maintenance
|
|
||||||
|
|
||||||
See `docs/PAYMENT_SYSTEM.md` for detailed payment system documentation.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,71 +0,0 @@
|
|||||||
# Project Structure Updates Summary
|
|
||||||
|
|
||||||
## What Was Changed
|
|
||||||
|
|
||||||
### ✅ **Folder Structure Cleanup**
|
|
||||||
- **Root directory cleaned**: Moved test files to `tests/`, documentation to `docs/`
|
|
||||||
- **Template organization**: Agent templates moved to their respective app directories
|
|
||||||
- **Orphaned templates removed**: Deleted unused agent templates (5 legacy agents)
|
|
||||||
- **Clean structure**: Now follows Django best practices
|
|
||||||
|
|
||||||
### ✅ **Updated Documentation**
|
|
||||||
|
|
||||||
#### **1. AGENT_SETUP_CHECKLIST.md**
|
|
||||||
- Added **Step 6: Verify Template Structure**
|
|
||||||
- Updated testing section with template verification commands
|
|
||||||
- Added troubleshooting for `TemplateDoesNotExist` errors
|
|
||||||
- Enhanced testing flow with authentication requirements
|
|
||||||
|
|
||||||
#### **2. MANUAL_AGENT_CREATION_GUIDE.md**
|
|
||||||
- Updated template troubleshooting section
|
|
||||||
- Added template location verification commands
|
|
||||||
- Clarified correct template structure within agent apps
|
|
||||||
|
|
||||||
#### **3. CLAUDE.md**
|
|
||||||
- Added project structure diagram
|
|
||||||
- Updated template organization section
|
|
||||||
- Replaced "Legacy vs New" with "Current Architecture"
|
|
||||||
- Added best practices for clean structure
|
|
||||||
|
|
||||||
## New Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
netcop_django/
|
|
||||||
├── 📁 docs/ # ← All guides and documentation
|
|
||||||
├── 📁 tests/ # ← All test files
|
|
||||||
├── 📁 agent_base/ # Agent framework
|
|
||||||
├── 📁 authentication/ # User management
|
|
||||||
├── 📁 core/ # Main functionality
|
|
||||||
├── 📁 wallet/ # Payment system
|
|
||||||
├── 📁 weather_reporter/ # Individual agent
|
|
||||||
│ └── templates/ # ← Agent templates HERE (detail.html)
|
|
||||||
├── 📁 templates/ # Global templates only
|
|
||||||
├── 📁 static/ # Static assets
|
|
||||||
├── 📁 media/ # User uploads
|
|
||||||
├── 📁 netcop_hub/ # Django project
|
|
||||||
└── manage.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Benefits
|
|
||||||
|
|
||||||
1. **📁 Clean Organization**: Everything in logical places
|
|
||||||
2. **🔧 Easy Maintenance**: Clear separation of concerns
|
|
||||||
3. **📈 Scalable**: Ready for new agents
|
|
||||||
4. **🚀 Professional**: Follows Django best practices
|
|
||||||
5. **🎯 Developer Friendly**: Easy to navigate and understand
|
|
||||||
|
|
||||||
## Important Notes
|
|
||||||
|
|
||||||
- **Template Location**: Agent templates should be in `agent_name/templates/detail.html` (simplified structure)
|
|
||||||
- **Restart Required**: Django server must be restarted after moving templates
|
|
||||||
- **Testing**: Use the new template verification commands to ensure correct setup
|
|
||||||
- **Documentation**: All guides now reflect the clean structure
|
|
||||||
|
|
||||||
## For Developers
|
|
||||||
|
|
||||||
When creating new agents:
|
|
||||||
1. Use `create_agent` command for automated setup
|
|
||||||
2. Follow the updated **AGENT_SETUP_CHECKLIST.md**
|
|
||||||
3. Place templates in agent app directories
|
|
||||||
4. Test template loading before deployment
|
|
||||||
5. Keep root directory clean using `docs/` and `tests/` folders
|
|
||||||
@ -1,607 +0,0 @@
|
|||||||
# Systematic Implementation Workflow
|
|
||||||
|
|
||||||
A comprehensive workflow with validation gates to ensure error-free template implementation.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This workflow provides a structured approach to implementing template changes with built-in quality gates and validation checkpoints. It prevents the implementation failures that occurred with the Social Ads Generator by ensuring systematic execution and thorough validation at each step.
|
|
||||||
|
|
||||||
## Workflow Architecture
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
|
||||||
│ SYSTEMATIC IMPLEMENTATION WORKFLOW │
|
|
||||||
├─────────────────────────────────────────────────────────────────┤
|
|
||||||
│ │
|
|
||||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
||||||
│ │ GATE 1 │ │ GATE 2 │ │ GATE 3 │ │
|
|
||||||
│ │Requirements │ │ Analysis │ │ Planning │ │
|
|
||||||
│ │ Validation │ │ Validation │ │ Validation │ │
|
|
||||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|
||||||
│ │ │ │ │
|
|
||||||
│ ▼ ▼ ▼ │
|
|
||||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
||||||
│ │ PHASE 1 │ │ PHASE 2 │ │ PHASE 3 │ │
|
|
||||||
│ │ Foundation │ │ Core Impl │ │ Integration │ │
|
|
||||||
│ │ Setup │ │ │ │ & Testing │ │
|
|
||||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|
||||||
│ │ │ │ │
|
|
||||||
│ ▼ ▼ ▼ │
|
|
||||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
||||||
│ │ GATE 4 │ │ GATE 5 │ │ GATE 6 │ │
|
|
||||||
│ │ Foundation │ │ Integration │ │ Final │ │
|
|
||||||
│ │ Validation │ │ Validation │ │ Validation │ │
|
|
||||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|
||||||
│ │
|
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quality Gates
|
|
||||||
|
|
||||||
### Gate 1: Requirements Validation
|
|
||||||
|
|
||||||
**Purpose**: Ensure complete understanding of all requirements before starting implementation.
|
|
||||||
|
|
||||||
**Validation Criteria:**
|
|
||||||
- [ ] All user requirements clearly documented
|
|
||||||
- [ ] All implicit requirements identified
|
|
||||||
- [ ] Success criteria defined
|
|
||||||
- [ ] Constraints and limitations understood
|
|
||||||
- [ ] Stakeholder expectations aligned
|
|
||||||
|
|
||||||
**Gate 1 Checklist:**
|
|
||||||
```markdown
|
|
||||||
# Gate 1: Requirements Validation
|
|
||||||
|
|
||||||
## Requirements Documentation
|
|
||||||
- [ ] User request fully analyzed
|
|
||||||
- [ ] Explicit requirements listed
|
|
||||||
- [ ] Implicit requirements identified
|
|
||||||
- [ ] Success criteria defined
|
|
||||||
- [ ] Constraints documented
|
|
||||||
|
|
||||||
## Stakeholder Alignment
|
|
||||||
- [ ] User expectations understood
|
|
||||||
- [ ] Business requirements considered
|
|
||||||
- [ ] Technical constraints acknowledged
|
|
||||||
- [ ] Quality standards defined
|
|
||||||
- [ ] Timeline agreed upon
|
|
||||||
|
|
||||||
## Completeness Check
|
|
||||||
- [ ] All requirements captured
|
|
||||||
- [ ] No ambiguities remain
|
|
||||||
- [ ] Edge cases considered
|
|
||||||
- [ ] Error conditions identified
|
|
||||||
- [ ] Performance requirements defined
|
|
||||||
|
|
||||||
## Approval
|
|
||||||
- [ ] Requirements reviewed
|
|
||||||
- [ ] Stakeholder approval obtained
|
|
||||||
- [ ] Implementation authorized
|
|
||||||
- [ ] Resources allocated
|
|
||||||
- [ ] Timeline confirmed
|
|
||||||
|
|
||||||
**Gate 1 Status**: [ ] PASS [ ] FAIL [ ] PENDING
|
|
||||||
**Gate 1 Approver**: [Name and Date]
|
|
||||||
**Gate 1 Notes**: [Additional notes]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Gate 2: Analysis Validation
|
|
||||||
|
|
||||||
**Purpose**: Ensure complete analysis of source and target templates before implementation.
|
|
||||||
|
|
||||||
**Validation Criteria:**
|
|
||||||
- [ ] Source template completely analyzed
|
|
||||||
- [ ] Target template current state documented
|
|
||||||
- [ ] Gap analysis completed
|
|
||||||
- [ ] Change requirements identified
|
|
||||||
- [ ] Implementation approach defined
|
|
||||||
|
|
||||||
**Gate 2 Checklist:**
|
|
||||||
```markdown
|
|
||||||
# Gate 2: Analysis Validation
|
|
||||||
|
|
||||||
## Source Template Analysis
|
|
||||||
- [ ] Complete HTML structure mapped
|
|
||||||
- [ ] All CSS classes documented
|
|
||||||
- [ ] All JavaScript functions analyzed
|
|
||||||
- [ ] All interactions identified
|
|
||||||
- [ ] All dependencies mapped
|
|
||||||
|
|
||||||
## Target Template Analysis
|
|
||||||
- [ ] Current state documented
|
|
||||||
- [ ] Existing components inventoried
|
|
||||||
- [ ] Code quality assessed
|
|
||||||
- [ ] Performance baseline established
|
|
||||||
- [ ] Technical debt identified
|
|
||||||
|
|
||||||
## Gap Analysis
|
|
||||||
- [ ] Structural differences identified
|
|
||||||
- [ ] Styling differences documented
|
|
||||||
- [ ] Functional differences analyzed
|
|
||||||
- [ ] Missing elements listed
|
|
||||||
- [ ] Excessive elements identified
|
|
||||||
|
|
||||||
## Change Requirements
|
|
||||||
- [ ] All changes clearly defined
|
|
||||||
- [ ] Change priorities assigned
|
|
||||||
- [ ] Implementation order planned
|
|
||||||
- [ ] Dependencies identified
|
|
||||||
- [ ] Risk assessment completed
|
|
||||||
|
|
||||||
**Gate 2 Status**: [ ] PASS [ ] FAIL [ ] PENDING
|
|
||||||
**Gate 2 Approver**: [Name and Date]
|
|
||||||
**Gate 2 Notes**: [Additional notes]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Gate 3: Planning Validation
|
|
||||||
|
|
||||||
**Purpose**: Ensure comprehensive implementation plan before starting development.
|
|
||||||
|
|
||||||
**Validation Criteria:**
|
|
||||||
- [ ] Detailed implementation plan created
|
|
||||||
- [ ] Risk mitigation strategies defined
|
|
||||||
- [ ] Resource requirements identified
|
|
||||||
- [ ] Timeline established
|
|
||||||
- [ ] Quality assurance plan prepared
|
|
||||||
|
|
||||||
**Gate 3 Checklist:**
|
|
||||||
```markdown
|
|
||||||
# Gate 3: Planning Validation
|
|
||||||
|
|
||||||
## Implementation Plan
|
|
||||||
- [ ] Step-by-step plan created
|
|
||||||
- [ ] Dependencies identified
|
|
||||||
- [ ] Resource requirements defined
|
|
||||||
- [ ] Timeline established
|
|
||||||
- [ ] Milestones defined
|
|
||||||
|
|
||||||
## Risk Management
|
|
||||||
- [ ] All risks identified
|
|
||||||
- [ ] Risk probabilities assessed
|
|
||||||
- [ ] Risk impacts evaluated
|
|
||||||
- [ ] Mitigation strategies defined
|
|
||||||
- [ ] Contingency plans prepared
|
|
||||||
|
|
||||||
## Quality Assurance
|
|
||||||
- [ ] Testing strategy defined
|
|
||||||
- [ ] Validation criteria established
|
|
||||||
- [ ] Review process planned
|
|
||||||
- [ ] Rollback procedures prepared
|
|
||||||
- [ ] Monitoring plan created
|
|
||||||
|
|
||||||
## Resource Allocation
|
|
||||||
- [ ] Time allocation confirmed
|
|
||||||
- [ ] Skill requirements identified
|
|
||||||
- [ ] Tool requirements defined
|
|
||||||
- [ ] Environment requirements set
|
|
||||||
- [ ] Support requirements planned
|
|
||||||
|
|
||||||
**Gate 3 Status**: [ ] PASS [ ] FAIL [ ] PENDING
|
|
||||||
**Gate 3 Approver**: [Name and Date]
|
|
||||||
**Gate 3 Notes**: [Additional notes]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Gate 4: Foundation Validation
|
|
||||||
|
|
||||||
**Purpose**: Ensure proper foundation setup before core implementation.
|
|
||||||
|
|
||||||
**Validation Criteria:**
|
|
||||||
- [ ] Environment properly configured
|
|
||||||
- [ ] Backups created
|
|
||||||
- [ ] Base structure implemented
|
|
||||||
- [ ] Foundation components validated
|
|
||||||
- [ ] Dependencies resolved
|
|
||||||
|
|
||||||
**Gate 4 Checklist:**
|
|
||||||
```markdown
|
|
||||||
# Gate 4: Foundation Validation
|
|
||||||
|
|
||||||
## Environment Setup
|
|
||||||
- [ ] Development environment configured
|
|
||||||
- [ ] Testing environment prepared
|
|
||||||
- [ ] Version control initialized
|
|
||||||
- [ ] Backup procedures verified
|
|
||||||
- [ ] Rollback capability confirmed
|
|
||||||
|
|
||||||
## Base Structure
|
|
||||||
- [ ] HTML structure foundation laid
|
|
||||||
- [ ] CSS architecture established
|
|
||||||
- [ ] JavaScript framework prepared
|
|
||||||
- [ ] Component templates created
|
|
||||||
- [ ] Naming conventions implemented
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
- [ ] External dependencies resolved
|
|
||||||
- [ ] Internal dependencies mapped
|
|
||||||
- [ ] Resource dependencies confirmed
|
|
||||||
- [ ] Technical dependencies verified
|
|
||||||
- [ ] Process dependencies established
|
|
||||||
|
|
||||||
## Foundation Testing
|
|
||||||
- [ ] Base structure validated
|
|
||||||
- [ ] Foundation components tested
|
|
||||||
- [ ] Dependencies verified
|
|
||||||
- [ ] Environment stability confirmed
|
|
||||||
- [ ] Performance baseline established
|
|
||||||
|
|
||||||
**Gate 4 Status**: [ ] PASS [ ] FAIL [ ] PENDING
|
|
||||||
**Gate 4 Approver**: [Name and Date]
|
|
||||||
**Gate 4 Notes**: [Additional notes]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Gate 5: Integration Validation
|
|
||||||
|
|
||||||
**Purpose**: Ensure all components work together correctly before final validation.
|
|
||||||
|
|
||||||
**Validation Criteria:**
|
|
||||||
- [ ] All components implemented
|
|
||||||
- [ ] Component integration verified
|
|
||||||
- [ ] Functionality tested
|
|
||||||
- [ ] Performance validated
|
|
||||||
- [ ] Security verified
|
|
||||||
|
|
||||||
**Gate 5 Checklist:**
|
|
||||||
```markdown
|
|
||||||
# Gate 5: Integration Validation
|
|
||||||
|
|
||||||
## Component Implementation
|
|
||||||
- [ ] All HTML components implemented
|
|
||||||
- [ ] All CSS styles applied
|
|
||||||
- [ ] All JavaScript functions working
|
|
||||||
- [ ] All interactions functional
|
|
||||||
- [ ] All responsive design working
|
|
||||||
|
|
||||||
## Integration Testing
|
|
||||||
- [ ] Components work together
|
|
||||||
- [ ] No conflicts between components
|
|
||||||
- [ ] Data flow verified
|
|
||||||
- [ ] State management working
|
|
||||||
- [ ] Error handling functional
|
|
||||||
|
|
||||||
## Functional Testing
|
|
||||||
- [ ] All user interactions work
|
|
||||||
- [ ] All business logic correct
|
|
||||||
- [ ] All edge cases handled
|
|
||||||
- [ ] All error conditions managed
|
|
||||||
- [ ] All performance requirements met
|
|
||||||
|
|
||||||
## Technical Validation
|
|
||||||
- [ ] Code quality standards met
|
|
||||||
- [ ] Security requirements satisfied
|
|
||||||
- [ ] Accessibility standards met
|
|
||||||
- [ ] Browser compatibility verified
|
|
||||||
- [ ] Performance benchmarks achieved
|
|
||||||
|
|
||||||
**Gate 5 Status**: [ ] PASS [ ] FAIL [ ] PENDING
|
|
||||||
**Gate 5 Approver**: [Name and Date]
|
|
||||||
**Gate 5 Notes**: [Additional notes]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Gate 6: Final Validation
|
|
||||||
|
|
||||||
**Purpose**: Ensure implementation meets all requirements and is ready for deployment.
|
|
||||||
|
|
||||||
**Validation Criteria:**
|
|
||||||
- [ ] All requirements satisfied
|
|
||||||
- [ ] Visual appearance matches expectations
|
|
||||||
- [ ] All functionality working correctly
|
|
||||||
- [ ] Performance meets standards
|
|
||||||
- [ ] User acceptance achieved
|
|
||||||
|
|
||||||
**Gate 6 Checklist:**
|
|
||||||
```markdown
|
|
||||||
# Gate 6: Final Validation
|
|
||||||
|
|
||||||
## Requirements Satisfaction
|
|
||||||
- [ ] All explicit requirements met
|
|
||||||
- [ ] All implicit requirements addressed
|
|
||||||
- [ ] All success criteria achieved
|
|
||||||
- [ ] All constraints respected
|
|
||||||
- [ ] All stakeholder expectations met
|
|
||||||
|
|
||||||
## Visual Validation
|
|
||||||
- [ ] Pixel-perfect match with source
|
|
||||||
- [ ] Responsive design working
|
|
||||||
- [ ] Visual consistency maintained
|
|
||||||
- [ ] Brand guidelines followed
|
|
||||||
- [ ] Accessibility visual standards met
|
|
||||||
|
|
||||||
## Functional Validation
|
|
||||||
- [ ] All functionality working
|
|
||||||
- [ ] All interactions smooth
|
|
||||||
- [ ] All error handling proper
|
|
||||||
- [ ] All performance acceptable
|
|
||||||
- [ ] All security measures active
|
|
||||||
|
|
||||||
## User Acceptance
|
|
||||||
- [ ] User testing completed
|
|
||||||
- [ ] User feedback incorporated
|
|
||||||
- [ ] User satisfaction achieved
|
|
||||||
- [ ] User training completed
|
|
||||||
- [ ] User documentation provided
|
|
||||||
|
|
||||||
**Gate 6 Status**: [ ] PASS [ ] FAIL [ ] PENDING
|
|
||||||
**Gate 6 Approver**: [Name and Date]
|
|
||||||
**Gate 6 Notes**: [Additional notes]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Implementation Phases
|
|
||||||
|
|
||||||
### Phase 1: Foundation Setup
|
|
||||||
|
|
||||||
**Objective**: Establish solid foundation for implementation.
|
|
||||||
|
|
||||||
**Activities:**
|
|
||||||
1. **Environment Preparation**
|
|
||||||
- Set up development environment
|
|
||||||
- Configure testing environment
|
|
||||||
- Prepare version control
|
|
||||||
- Create backup systems
|
|
||||||
|
|
||||||
2. **Structure Foundation**
|
|
||||||
- Implement base HTML structure
|
|
||||||
- Set up CSS architecture
|
|
||||||
- Prepare JavaScript framework
|
|
||||||
- Create component templates
|
|
||||||
|
|
||||||
3. **Dependency Management**
|
|
||||||
- Resolve external dependencies
|
|
||||||
- Map internal dependencies
|
|
||||||
- Verify resource availability
|
|
||||||
- Establish process dependencies
|
|
||||||
|
|
||||||
**Phase 1 Deliverables:**
|
|
||||||
- [ ] Configured development environment
|
|
||||||
- [ ] Base template structure
|
|
||||||
- [ ] CSS architecture foundation
|
|
||||||
- [ ] JavaScript framework setup
|
|
||||||
- [ ] Component templates
|
|
||||||
- [ ] Dependency mapping
|
|
||||||
- [ ] Backup and rollback procedures
|
|
||||||
|
|
||||||
### Phase 2: Core Implementation
|
|
||||||
|
|
||||||
**Objective**: Implement all core components and functionality.
|
|
||||||
|
|
||||||
**Activities:**
|
|
||||||
1. **HTML Structure Implementation**
|
|
||||||
- Update header structure
|
|
||||||
- Implement main content layout
|
|
||||||
- Create sidebar components
|
|
||||||
- Add footer elements
|
|
||||||
|
|
||||||
2. **CSS Styling Implementation**
|
|
||||||
- Apply design token system
|
|
||||||
- Implement component styles
|
|
||||||
- Add responsive design rules
|
|
||||||
- Create interaction styles
|
|
||||||
|
|
||||||
3. **JavaScript Functionality**
|
|
||||||
- Implement core functions
|
|
||||||
- Add event listeners
|
|
||||||
- Create state management
|
|
||||||
- Add error handling
|
|
||||||
|
|
||||||
**Phase 2 Deliverables:**
|
|
||||||
- [ ] Complete HTML structure
|
|
||||||
- [ ] Full CSS implementation
|
|
||||||
- [ ] Working JavaScript functionality
|
|
||||||
- [ ] Responsive design
|
|
||||||
- [ ] Interactive elements
|
|
||||||
- [ ] Error handling
|
|
||||||
- [ ] Performance optimization
|
|
||||||
|
|
||||||
### Phase 3: Integration and Testing
|
|
||||||
|
|
||||||
**Objective**: Ensure all components work together seamlessly.
|
|
||||||
|
|
||||||
**Activities:**
|
|
||||||
1. **Component Integration**
|
|
||||||
- Test component interactions
|
|
||||||
- Verify data flow
|
|
||||||
- Validate state management
|
|
||||||
- Check error propagation
|
|
||||||
|
|
||||||
2. **System Testing**
|
|
||||||
- Test complete user workflows
|
|
||||||
- Verify cross-browser compatibility
|
|
||||||
- Test responsive behavior
|
|
||||||
- Validate accessibility
|
|
||||||
|
|
||||||
3. **Performance Validation**
|
|
||||||
- Test loading performance
|
|
||||||
- Verify runtime performance
|
|
||||||
- Check memory usage
|
|
||||||
- Validate network efficiency
|
|
||||||
|
|
||||||
**Phase 3 Deliverables:**
|
|
||||||
- [ ] Integrated system
|
|
||||||
- [ ] Comprehensive test results
|
|
||||||
- [ ] Performance benchmarks
|
|
||||||
- [ ] Accessibility compliance
|
|
||||||
- [ ] Browser compatibility report
|
|
||||||
- [ ] User acceptance validation
|
|
||||||
|
|
||||||
## Implementation Workflow Process
|
|
||||||
|
|
||||||
### Workflow Execution Steps
|
|
||||||
|
|
||||||
1. **Pre-Implementation**
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
|
||||||
│ PRE-IMPLEMENTATION PHASE │
|
|
||||||
├─────────────────────────────────────────────────────────────────┤
|
|
||||||
│ 1. Requirements Analysis │
|
|
||||||
│ 2. Source Template Analysis │
|
|
||||||
│ 3. Target Template Analysis │
|
|
||||||
│ 4. Gap Analysis │
|
|
||||||
│ 5. Implementation Planning │
|
|
||||||
│ 6. Risk Assessment │
|
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Implementation**
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
|
||||||
│ IMPLEMENTATION PHASE │
|
|
||||||
├─────────────────────────────────────────────────────────────────┤
|
|
||||||
│ Phase 1: Foundation Setup │
|
|
||||||
│ ├─ Environment Configuration │
|
|
||||||
│ ├─ Base Structure Implementation │
|
|
||||||
│ ├─ Dependency Resolution │
|
|
||||||
│ └─ Foundation Validation (Gate 4) │
|
|
||||||
│ │
|
|
||||||
│ Phase 2: Core Implementation │
|
|
||||||
│ ├─ HTML Structure Implementation │
|
|
||||||
│ ├─ CSS Styling Implementation │
|
|
||||||
│ ├─ JavaScript Functionality │
|
|
||||||
│ └─ Component Testing │
|
|
||||||
│ │
|
|
||||||
│ Phase 3: Integration and Testing │
|
|
||||||
│ ├─ Component Integration │
|
|
||||||
│ ├─ System Testing │
|
|
||||||
│ ├─ Performance Validation │
|
|
||||||
│ └─ Integration Validation (Gate 5) │
|
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Post-Implementation**
|
|
||||||
```
|
|
||||||
┌─────────────────────────────────────────────────────────────────┐
|
|
||||||
│ POST-IMPLEMENTATION PHASE │
|
|
||||||
├─────────────────────────────────────────────────────────────────┤
|
|
||||||
│ 1. Final Validation (Gate 6) │
|
|
||||||
│ 2. User Acceptance Testing │
|
|
||||||
│ 3. Documentation Update │
|
|
||||||
│ 4. Knowledge Capture │
|
|
||||||
│ 5. Deployment │
|
|
||||||
│ 6. Monitoring and Support │
|
|
||||||
└─────────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
### Quality Control Measures
|
|
||||||
|
|
||||||
**Continuous Validation:**
|
|
||||||
- [ ] Regular checkpoint reviews
|
|
||||||
- [ ] Automated testing integration
|
|
||||||
- [ ] Peer review processes
|
|
||||||
- [ ] User feedback collection
|
|
||||||
- [ ] Performance monitoring
|
|
||||||
|
|
||||||
**Error Prevention:**
|
|
||||||
- [ ] Comprehensive planning
|
|
||||||
- [ ] Systematic execution
|
|
||||||
- [ ] Regular validation
|
|
||||||
- [ ] Risk mitigation
|
|
||||||
- [ ] Contingency planning
|
|
||||||
|
|
||||||
**Quality Assurance:**
|
|
||||||
- [ ] Code quality standards
|
|
||||||
- [ ] Design consistency checks
|
|
||||||
- [ ] Performance benchmarks
|
|
||||||
- [ ] Security validation
|
|
||||||
- [ ] Accessibility compliance
|
|
||||||
|
|
||||||
## Workflow Tools and Templates
|
|
||||||
|
|
||||||
### Implementation Tracking Template
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Implementation Tracking: [Task Name]
|
|
||||||
|
|
||||||
## Project Information
|
|
||||||
- **Project**: [Project Name]
|
|
||||||
- **Start Date**: [Date]
|
|
||||||
- **Target Date**: [Date]
|
|
||||||
- **Implementer**: [Name]
|
|
||||||
- **Reviewer**: [Name]
|
|
||||||
|
|
||||||
## Gate Status
|
|
||||||
- **Gate 1 (Requirements)**: [ ] PASS [ ] FAIL [ ] PENDING
|
|
||||||
- **Gate 2 (Analysis)**: [ ] PASS [ ] FAIL [ ] PENDING
|
|
||||||
- **Gate 3 (Planning)**: [ ] PASS [ ] FAIL [ ] PENDING
|
|
||||||
- **Gate 4 (Foundation)**: [ ] PASS [ ] FAIL [ ] PENDING
|
|
||||||
- **Gate 5 (Integration)**: [ ] PASS [ ] FAIL [ ] PENDING
|
|
||||||
- **Gate 6 (Final)**: [ ] PASS [ ] FAIL [ ] PENDING
|
|
||||||
|
|
||||||
## Phase Status
|
|
||||||
- **Phase 1 (Foundation)**: [ ] NOT STARTED [ ] IN PROGRESS [ ] COMPLETED
|
|
||||||
- **Phase 2 (Core)**: [ ] NOT STARTED [ ] IN PROGRESS [ ] COMPLETED
|
|
||||||
- **Phase 3 (Integration)**: [ ] NOT STARTED [ ] IN PROGRESS [ ] COMPLETED
|
|
||||||
|
|
||||||
## Issues and Risks
|
|
||||||
- **Open Issues**: [Count]
|
|
||||||
- **Resolved Issues**: [Count]
|
|
||||||
- **Active Risks**: [Count]
|
|
||||||
- **Mitigated Risks**: [Count]
|
|
||||||
|
|
||||||
## Progress Metrics
|
|
||||||
- **Overall Progress**: [Percentage]
|
|
||||||
- **Requirements Complete**: [Percentage]
|
|
||||||
- **Implementation Complete**: [Percentage]
|
|
||||||
- **Testing Complete**: [Percentage]
|
|
||||||
- **Documentation Complete**: [Percentage]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Daily Implementation Checklist
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Daily Implementation Checklist
|
|
||||||
|
|
||||||
## Pre-Work
|
|
||||||
- [ ] Review previous day's progress
|
|
||||||
- [ ] Check for any blocking issues
|
|
||||||
- [ ] Verify environment is ready
|
|
||||||
- [ ] Confirm current phase objectives
|
|
||||||
|
|
||||||
## During Work
|
|
||||||
- [ ] Follow systematic workflow
|
|
||||||
- [ ] Document progress regularly
|
|
||||||
- [ ] Test changes incrementally
|
|
||||||
- [ ] Validate against requirements
|
|
||||||
|
|
||||||
## Post-Work
|
|
||||||
- [ ] Update progress tracking
|
|
||||||
- [ ] Document any issues encountered
|
|
||||||
- [ ] Plan next day's activities
|
|
||||||
- [ ] Commit changes to version control
|
|
||||||
|
|
||||||
## Quality Checks
|
|
||||||
- [ ] Code quality maintained
|
|
||||||
- [ ] Performance not degraded
|
|
||||||
- [ ] Security measures intact
|
|
||||||
- [ ] Accessibility preserved
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase Completion Checklist
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Phase Completion Checklist: Phase [Number]
|
|
||||||
|
|
||||||
## Deliverables
|
|
||||||
- [ ] All planned deliverables completed
|
|
||||||
- [ ] Quality standards met
|
|
||||||
- [ ] Documentation updated
|
|
||||||
- [ ] Testing completed
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
- [ ] Peer review conducted
|
|
||||||
- [ ] User feedback incorporated
|
|
||||||
- [ ] Performance benchmarks met
|
|
||||||
- [ ] Security validation passed
|
|
||||||
|
|
||||||
## Transition
|
|
||||||
- [ ] Next phase planned
|
|
||||||
- [ ] Resources allocated
|
|
||||||
- [ ] Dependencies resolved
|
|
||||||
- [ ] Risks assessed
|
|
||||||
|
|
||||||
## Approval
|
|
||||||
- [ ] Phase review completed
|
|
||||||
- [ ] Stakeholder approval obtained
|
|
||||||
- [ ] Gate criteria met
|
|
||||||
- [ ] Next phase authorized
|
|
||||||
```
|
|
||||||
|
|
||||||
This systematic workflow ensures that every implementation follows a structured approach with built-in quality gates and validation checkpoints, preventing the issues that occurred with the Social Ads Generator initial implementation.
|
|
||||||
@ -1,910 +0,0 @@
|
|||||||
# Template Architecture Patterns
|
|
||||||
|
|
||||||
Technical reference for implementing optimized Django templates with modern frontend patterns.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This document provides detailed technical specifications for the template architecture used in optimized agents. The patterns ensure consistent UI/UX, maintainable code, and optimal performance.
|
|
||||||
|
|
||||||
## Core Architecture Principles
|
|
||||||
|
|
||||||
### 1. Widget-Based Component System
|
|
||||||
- **Modular Design**: Each UI component is self-contained
|
|
||||||
- **Reusable Patterns**: Components can be easily replicated across agents
|
|
||||||
- **Consistent Styling**: All components follow the same design system
|
|
||||||
- **Responsive Layout**: Components adapt to different screen sizes
|
|
||||||
|
|
||||||
### 2. Self-Contained Styles
|
|
||||||
- **No External Dependencies**: All styles are embedded in the template
|
|
||||||
- **CSS Custom Properties**: Centralized design tokens for consistency
|
|
||||||
- **Optimized Performance**: Reduced HTTP requests and faster loading
|
|
||||||
- **Maintainable Code**: Easy to update and modify styles
|
|
||||||
|
|
||||||
### 3. Security-First JavaScript
|
|
||||||
- **HTML Sanitization**: All dynamic content is sanitized
|
|
||||||
- **XSS Prevention**: Input validation and output encoding
|
|
||||||
- **Safe DOM Manipulation**: Controlled content insertion
|
|
||||||
- **Event Management**: Proper listener cleanup and memory management
|
|
||||||
|
|
||||||
## Template Structure
|
|
||||||
|
|
||||||
### Base Template Integration
|
|
||||||
```html
|
|
||||||
{% extends 'base.html' %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block title %}Agent Name - NetCop AI Hub{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_css %}
|
|
||||||
<style>
|
|
||||||
/* Agent-specific optimized styles */
|
|
||||||
</style>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<!-- Agent content -->
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_js %}
|
|
||||||
<script>
|
|
||||||
/* Agent-specific JavaScript */
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Layout Grid System
|
|
||||||
```html
|
|
||||||
<div class="agent-container">
|
|
||||||
<div class="agent-grid">
|
|
||||||
<!-- Main Content Area -->
|
|
||||||
<div class="agent-main">
|
|
||||||
<div class="agent-header">
|
|
||||||
<h1>{{ agent.name }}</h1>
|
|
||||||
<p>{{ agent.description }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="agent-form">
|
|
||||||
<!-- Form components -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="agent-output">
|
|
||||||
<!-- Output display -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Sidebar Widgets -->
|
|
||||||
<div class="agent-sidebar">
|
|
||||||
<!-- Widget components -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
## CSS Architecture
|
|
||||||
|
|
||||||
### Design System Variables
|
|
||||||
```css
|
|
||||||
:root {
|
|
||||||
/* Color Palette */
|
|
||||||
--primary: #000000;
|
|
||||||
--surface: #ffffff;
|
|
||||||
--surface-variant: #f8fafc;
|
|
||||||
--background: #f3f4f6;
|
|
||||||
--outline: #e4e7eb;
|
|
||||||
--outline-variant: #e1e4e7;
|
|
||||||
--on-surface: #1a1a1a;
|
|
||||||
--on-surface-variant: #6b7280;
|
|
||||||
--success: #10b981;
|
|
||||||
--error: #ef4444;
|
|
||||||
--warning: #f59e0b;
|
|
||||||
--info: #3b82f6;
|
|
||||||
|
|
||||||
/* Border Radius */
|
|
||||||
--radius-xs: 4px;
|
|
||||||
--radius-sm: 8px;
|
|
||||||
--radius-md: 12px;
|
|
||||||
--radius-lg: 16px;
|
|
||||||
--radius-xl: 20px;
|
|
||||||
--radius-2xl: 24px;
|
|
||||||
--radius-full: 9999px;
|
|
||||||
|
|
||||||
/* Spacing Scale */
|
|
||||||
--spacing-xs: 4px;
|
|
||||||
--spacing-sm: 8px;
|
|
||||||
--spacing-md: 16px;
|
|
||||||
--spacing-lg: 24px;
|
|
||||||
--spacing-xl: 32px;
|
|
||||||
--spacing-2xl: 48px;
|
|
||||||
--spacing-3xl: 64px;
|
|
||||||
|
|
||||||
/* Typography */
|
|
||||||
--font-size-xs: 0.75rem;
|
|
||||||
--font-size-sm: 0.875rem;
|
|
||||||
--font-size-base: 1rem;
|
|
||||||
--font-size-lg: 1.125rem;
|
|
||||||
--font-size-xl: 1.25rem;
|
|
||||||
--font-size-2xl: 1.5rem;
|
|
||||||
--font-size-3xl: 1.875rem;
|
|
||||||
|
|
||||||
/* Font Weights */
|
|
||||||
--font-weight-normal: 400;
|
|
||||||
--font-weight-medium: 500;
|
|
||||||
--font-weight-semibold: 600;
|
|
||||||
--font-weight-bold: 700;
|
|
||||||
|
|
||||||
/* Line Heights */
|
|
||||||
--line-height-tight: 1.25;
|
|
||||||
--line-height-normal: 1.5;
|
|
||||||
--line-height-relaxed: 1.75;
|
|
||||||
|
|
||||||
/* Shadows */
|
|
||||||
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05);
|
|
||||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
||||||
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
||||||
--shadow-lg: 0 10px 20px rgba(0, 0, 0, 0.15);
|
|
||||||
--shadow-xl: 0 20px 40px rgba(0, 0, 0, 0.2);
|
|
||||||
|
|
||||||
/* Transitions */
|
|
||||||
--transition-fast: 0.15s ease;
|
|
||||||
--transition-base: 0.2s ease;
|
|
||||||
--transition-slow: 0.3s ease;
|
|
||||||
|
|
||||||
/* Z-Index Scale */
|
|
||||||
--z-dropdown: 1000;
|
|
||||||
--z-sticky: 1020;
|
|
||||||
--z-fixed: 1030;
|
|
||||||
--z-modal-backdrop: 1040;
|
|
||||||
--z-modal: 1050;
|
|
||||||
--z-popover: 1060;
|
|
||||||
--z-tooltip: 1070;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Component Architecture
|
|
||||||
```css
|
|
||||||
/* Base Component Styles */
|
|
||||||
.component {
|
|
||||||
/* Use design system variables */
|
|
||||||
background: var(--surface);
|
|
||||||
border: 1px solid var(--outline);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
transition: var(--transition-base);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Widget Base Class */
|
|
||||||
.widget {
|
|
||||||
@extend .component;
|
|
||||||
margin-bottom: var(--spacing-md);
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget:hover {
|
|
||||||
box-shadow: var(--shadow-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-title {
|
|
||||||
font-size: var(--font-size-lg);
|
|
||||||
font-weight: var(--font-weight-semibold);
|
|
||||||
color: var(--on-surface);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.widget-content {
|
|
||||||
color: var(--on-surface-variant);
|
|
||||||
line-height: var(--line-height-normal);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Grid System
|
|
||||||
```css
|
|
||||||
/* Responsive Grid Layout */
|
|
||||||
.agent-container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: var(--spacing-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 300px;
|
|
||||||
gap: var(--spacing-xl);
|
|
||||||
align-items: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive Breakpoints */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.agent-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: var(--spacing-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-sidebar {
|
|
||||||
order: -1; /* Move sidebar to top on mobile */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.agent-container {
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-grid {
|
|
||||||
gap: var(--spacing-md);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Form Component Patterns
|
|
||||||
```css
|
|
||||||
/* Form Base Styles */
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: var(--spacing-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-label {
|
|
||||||
display: block;
|
|
||||||
font-weight: var(--font-weight-medium);
|
|
||||||
color: var(--on-surface);
|
|
||||||
margin-bottom: var(--spacing-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control {
|
|
||||||
width: 100%;
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
border: 1px solid var(--outline);
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
font-size: var(--font-size-base);
|
|
||||||
background: var(--surface);
|
|
||||||
color: var(--on-surface);
|
|
||||||
transition: var(--transition-base);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--primary);
|
|
||||||
box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control.is-invalid {
|
|
||||||
border-color: var(--error);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control.is-valid {
|
|
||||||
border-color: var(--success);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form Validation Styles */
|
|
||||||
.form-error {
|
|
||||||
color: var(--error);
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
margin-top: var(--spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-help {
|
|
||||||
color: var(--on-surface-variant);
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
margin-top: var(--spacing-xs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Required Field Indicator */
|
|
||||||
.required::after {
|
|
||||||
content: " *";
|
|
||||||
color: var(--error);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Button Component System
|
|
||||||
```css
|
|
||||||
/* Button Base */
|
|
||||||
.btn {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: var(--spacing-sm) var(--spacing-md);
|
|
||||||
border: 1px solid transparent;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
font-size: var(--font-size-base);
|
|
||||||
font-weight: var(--font-weight-medium);
|
|
||||||
line-height: var(--line-height-tight);
|
|
||||||
text-decoration: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: var(--transition-base);
|
|
||||||
user-select: none;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:focus {
|
|
||||||
outline: none;
|
|
||||||
box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Button Variants */
|
|
||||||
.btn-primary {
|
|
||||||
background: var(--primary);
|
|
||||||
color: var(--surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover:not(:disabled) {
|
|
||||||
background: color-mix(in srgb, var(--primary) 90%, black);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary {
|
|
||||||
background: var(--surface-variant);
|
|
||||||
color: var(--on-surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary:hover:not(:disabled) {
|
|
||||||
background: color-mix(in srgb, var(--surface-variant) 90%, black);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-outline {
|
|
||||||
background: transparent;
|
|
||||||
color: var(--primary);
|
|
||||||
border-color: var(--primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-outline:hover:not(:disabled) {
|
|
||||||
background: var(--primary);
|
|
||||||
color: var(--surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Button Sizes */
|
|
||||||
.btn-sm {
|
|
||||||
padding: var(--spacing-xs) var(--spacing-sm);
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-lg {
|
|
||||||
padding: var(--spacing-md) var(--spacing-lg);
|
|
||||||
font-size: var(--font-size-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Button States */
|
|
||||||
.btn-loading {
|
|
||||||
position: relative;
|
|
||||||
color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-loading::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
margin: -8px 0 0 -8px;
|
|
||||||
border: 2px solid transparent;
|
|
||||||
border-top-color: currentColor;
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## JavaScript Architecture
|
|
||||||
|
|
||||||
### Module Pattern
|
|
||||||
```javascript
|
|
||||||
// Agent Module Pattern
|
|
||||||
const AgentModule = (function() {
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// Private variables
|
|
||||||
let isInitialized = false;
|
|
||||||
let eventListeners = [];
|
|
||||||
let config = {};
|
|
||||||
|
|
||||||
// Private methods
|
|
||||||
function init() {
|
|
||||||
if (isInitialized) return;
|
|
||||||
|
|
||||||
setupEventListeners();
|
|
||||||
setupFormValidation();
|
|
||||||
setupWalletIntegration();
|
|
||||||
setupAccessibility();
|
|
||||||
|
|
||||||
isInitialized = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupEventListeners() {
|
|
||||||
// Event listener setup with cleanup tracking
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupFormValidation() {
|
|
||||||
// Form validation setup
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupWalletIntegration() {
|
|
||||||
// Wallet integration setup
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupAccessibility() {
|
|
||||||
// Accessibility enhancements
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public API
|
|
||||||
return {
|
|
||||||
init: init,
|
|
||||||
destroy: function() {
|
|
||||||
// Cleanup method
|
|
||||||
eventListeners.forEach(({element, event, handler}) => {
|
|
||||||
if (element) {
|
|
||||||
element.removeEventListener(event, handler);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
eventListeners = [];
|
|
||||||
isInitialized = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Public methods
|
|
||||||
submitForm: function(formData) {
|
|
||||||
// Form submission logic
|
|
||||||
},
|
|
||||||
|
|
||||||
updateWalletBalance: function(newBalance) {
|
|
||||||
// Wallet balance update logic
|
|
||||||
},
|
|
||||||
|
|
||||||
showError: function(message) {
|
|
||||||
// Error display logic
|
|
||||||
},
|
|
||||||
|
|
||||||
showSuccess: function(message) {
|
|
||||||
// Success display logic
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Initialize when DOM is ready
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
AgentModule.init();
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### State Management Pattern
|
|
||||||
```javascript
|
|
||||||
// Simple State Management
|
|
||||||
const AgentState = {
|
|
||||||
// Initial state
|
|
||||||
data: {
|
|
||||||
isLoading: false,
|
|
||||||
walletBalance: 0,
|
|
||||||
formData: {},
|
|
||||||
results: null,
|
|
||||||
errors: []
|
|
||||||
},
|
|
||||||
|
|
||||||
// State update method
|
|
||||||
setState(newState) {
|
|
||||||
this.data = { ...this.data, ...newState };
|
|
||||||
this.render();
|
|
||||||
},
|
|
||||||
|
|
||||||
// Get current state
|
|
||||||
getState() {
|
|
||||||
return { ...this.data };
|
|
||||||
},
|
|
||||||
|
|
||||||
// Render method
|
|
||||||
render() {
|
|
||||||
// Update UI based on state
|
|
||||||
this.updateLoadingState();
|
|
||||||
this.updateWalletDisplay();
|
|
||||||
this.updateFormState();
|
|
||||||
this.updateResultsDisplay();
|
|
||||||
this.updateErrorDisplay();
|
|
||||||
},
|
|
||||||
|
|
||||||
updateLoadingState() {
|
|
||||||
const submitBtn = document.getElementById('submitBtn');
|
|
||||||
const loadingState = document.getElementById('loadingState');
|
|
||||||
|
|
||||||
if (this.data.isLoading) {
|
|
||||||
submitBtn.classList.add('btn-loading');
|
|
||||||
submitBtn.disabled = true;
|
|
||||||
loadingState.style.display = 'block';
|
|
||||||
} else {
|
|
||||||
submitBtn.classList.remove('btn-loading');
|
|
||||||
submitBtn.disabled = false;
|
|
||||||
loadingState.style.display = 'none';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateWalletDisplay() {
|
|
||||||
const walletElement = document.getElementById('walletBalance');
|
|
||||||
if (walletElement) {
|
|
||||||
walletElement.textContent = this.data.walletBalance.toFixed(2);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateFormState() {
|
|
||||||
// Update form based on state
|
|
||||||
},
|
|
||||||
|
|
||||||
updateResultsDisplay() {
|
|
||||||
const resultsContainer = document.getElementById('results');
|
|
||||||
if (this.data.results && resultsContainer) {
|
|
||||||
// Display results safely
|
|
||||||
safeSetHTML(resultsContainer, this.data.results);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateErrorDisplay() {
|
|
||||||
const errorContainer = document.getElementById('errorContainer');
|
|
||||||
if (errorContainer) {
|
|
||||||
errorContainer.innerHTML = '';
|
|
||||||
this.data.errors.forEach(error => {
|
|
||||||
const errorElement = document.createElement('div');
|
|
||||||
errorElement.className = 'alert alert-error';
|
|
||||||
errorElement.textContent = error;
|
|
||||||
errorContainer.appendChild(errorElement);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Event Management Pattern
|
|
||||||
```javascript
|
|
||||||
// Event Management Utility
|
|
||||||
const EventManager = {
|
|
||||||
listeners: new Map(),
|
|
||||||
|
|
||||||
add(element, event, handler, options = {}) {
|
|
||||||
const key = `${element.id || 'unknown'}-${event}`;
|
|
||||||
|
|
||||||
// Store reference for cleanup
|
|
||||||
if (!this.listeners.has(key)) {
|
|
||||||
this.listeners.set(key, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.listeners.get(key).push({
|
|
||||||
element,
|
|
||||||
event,
|
|
||||||
handler,
|
|
||||||
options
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add event listener
|
|
||||||
element.addEventListener(event, handler, options);
|
|
||||||
},
|
|
||||||
|
|
||||||
remove(element, event, handler) {
|
|
||||||
const key = `${element.id || 'unknown'}-${event}`;
|
|
||||||
const listeners = this.listeners.get(key);
|
|
||||||
|
|
||||||
if (listeners) {
|
|
||||||
const index = listeners.findIndex(l =>
|
|
||||||
l.element === element &&
|
|
||||||
l.event === event &&
|
|
||||||
l.handler === handler
|
|
||||||
);
|
|
||||||
|
|
||||||
if (index > -1) {
|
|
||||||
listeners.splice(index, 1);
|
|
||||||
element.removeEventListener(event, handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
removeAll() {
|
|
||||||
this.listeners.forEach(listeners => {
|
|
||||||
listeners.forEach(({element, event, handler}) => {
|
|
||||||
element.removeEventListener(event, handler);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.listeners.clear();
|
|
||||||
},
|
|
||||||
|
|
||||||
delegate(parent, selector, event, handler) {
|
|
||||||
const delegateHandler = (e) => {
|
|
||||||
const target = e.target.closest(selector);
|
|
||||||
if (target) {
|
|
||||||
handler.call(target, e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.add(parent, event, delegateHandler);
|
|
||||||
return delegateHandler;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Performance Optimization Patterns
|
|
||||||
|
|
||||||
### Lazy Loading
|
|
||||||
```javascript
|
|
||||||
// Lazy Loading Implementation
|
|
||||||
const LazyLoader = {
|
|
||||||
observers: new Map(),
|
|
||||||
|
|
||||||
init() {
|
|
||||||
if ('IntersectionObserver' in window) {
|
|
||||||
this.createObserver();
|
|
||||||
} else {
|
|
||||||
// Fallback for older browsers
|
|
||||||
this.loadAllContent();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
createObserver() {
|
|
||||||
const observer = new IntersectionObserver((entries) => {
|
|
||||||
entries.forEach(entry => {
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
this.loadContent(entry.target);
|
|
||||||
observer.unobserve(entry.target);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, {
|
|
||||||
rootMargin: '50px'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Observe elements with lazy loading
|
|
||||||
document.querySelectorAll('[data-lazy]').forEach(el => {
|
|
||||||
observer.observe(el);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
loadContent(element) {
|
|
||||||
const src = element.dataset.lazy;
|
|
||||||
if (src) {
|
|
||||||
if (element.tagName === 'IMG') {
|
|
||||||
element.src = src;
|
|
||||||
} else {
|
|
||||||
// Load other content types
|
|
||||||
fetch(src)
|
|
||||||
.then(response => response.text())
|
|
||||||
.then(html => {
|
|
||||||
safeSetHTML(element, html);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
loadAllContent() {
|
|
||||||
document.querySelectorAll('[data-lazy]').forEach(el => {
|
|
||||||
this.loadContent(el);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
### Resource Optimization
|
|
||||||
```javascript
|
|
||||||
// Resource Management
|
|
||||||
const ResourceManager = {
|
|
||||||
cache: new Map(),
|
|
||||||
|
|
||||||
// Cache frequently used data
|
|
||||||
cache(key, data, ttl = 300000) { // 5 minutes default
|
|
||||||
this.cache.set(key, {
|
|
||||||
data,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
ttl
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// Get cached data
|
|
||||||
get(key) {
|
|
||||||
const cached = this.cache.get(key);
|
|
||||||
if (cached && Date.now() - cached.timestamp < cached.ttl) {
|
|
||||||
return cached.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove expired cache
|
|
||||||
this.cache.delete(key);
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Clear expired cache
|
|
||||||
cleanup() {
|
|
||||||
const now = Date.now();
|
|
||||||
for (const [key, cached] of this.cache) {
|
|
||||||
if (now - cached.timestamp >= cached.ttl) {
|
|
||||||
this.cache.delete(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Preload resources
|
|
||||||
preload(urls) {
|
|
||||||
urls.forEach(url => {
|
|
||||||
const link = document.createElement('link');
|
|
||||||
link.rel = 'preload';
|
|
||||||
link.href = url;
|
|
||||||
link.as = 'fetch';
|
|
||||||
document.head.appendChild(link);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Integration Patterns
|
|
||||||
|
|
||||||
### Django Template Integration
|
|
||||||
```html
|
|
||||||
<!-- Dynamic Content with Security -->
|
|
||||||
<div class="widget" data-widget="wallet">
|
|
||||||
<h3>💰 Your Wallet</h3>
|
|
||||||
<div class="wallet-balance">
|
|
||||||
<span class="balance-amount" id="walletBalance">{{ user.wallet_balance|floatformat:2 }}</span>
|
|
||||||
<span class="balance-currency">AED</span>
|
|
||||||
</div>
|
|
||||||
<a href="{% url 'wallet_topup' %}" class="btn btn-primary btn-sm">
|
|
||||||
Top Up Wallet
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Form with CSRF and Validation -->
|
|
||||||
<form id="agentForm" method="post" enctype="multipart/form-data" class="agent-form">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<!-- Dynamic form fields -->
|
|
||||||
{% for field in form %}
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="{{ field.id_for_label }}" class="form-label">
|
|
||||||
{{ field.label }}
|
|
||||||
{% if field.field.required %}
|
|
||||||
<span class="required" aria-label="required">*</span>
|
|
||||||
{% endif %}
|
|
||||||
</label>
|
|
||||||
|
|
||||||
{{ field }}
|
|
||||||
|
|
||||||
{% if field.help_text %}
|
|
||||||
<div class="form-help">{{ field.help_text }}</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if field.errors %}
|
|
||||||
<div class="form-error">
|
|
||||||
{% for error in field.errors %}
|
|
||||||
{{ error }}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary" id="submitBtn">
|
|
||||||
Generate Content
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
```
|
|
||||||
|
|
||||||
### API Integration Pattern
|
|
||||||
```javascript
|
|
||||||
// API Communication
|
|
||||||
const ApiClient = {
|
|
||||||
baseURL: '/api/v1',
|
|
||||||
|
|
||||||
async request(endpoint, options = {}) {
|
|
||||||
const url = `${this.baseURL}${endpoint}`;
|
|
||||||
const config = {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-CSRFToken': this.getCSRFToken(),
|
|
||||||
...options.headers
|
|
||||||
},
|
|
||||||
...options
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url, config);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await response.json();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('API request failed:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getCSRFToken() {
|
|
||||||
return document.querySelector('[name=csrfmiddlewaretoken]')?.value || '';
|
|
||||||
},
|
|
||||||
|
|
||||||
async get(endpoint) {
|
|
||||||
return this.request(endpoint, { method: 'GET' });
|
|
||||||
},
|
|
||||||
|
|
||||||
async post(endpoint, data) {
|
|
||||||
return this.request(endpoint, {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify(data)
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async uploadFile(endpoint, formData) {
|
|
||||||
return this.request(endpoint, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'X-CSRFToken': this.getCSRFToken()
|
|
||||||
// Don't set Content-Type for FormData
|
|
||||||
},
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing Integration
|
|
||||||
|
|
||||||
### Component Testing
|
|
||||||
```javascript
|
|
||||||
// Component Test Utilities
|
|
||||||
const TestUtils = {
|
|
||||||
// Create test element
|
|
||||||
createElement(tag, attributes = {}, content = '') {
|
|
||||||
const element = document.createElement(tag);
|
|
||||||
|
|
||||||
Object.entries(attributes).forEach(([key, value]) => {
|
|
||||||
element.setAttribute(key, value);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (content) {
|
|
||||||
element.textContent = content;
|
|
||||||
}
|
|
||||||
|
|
||||||
return element;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Simulate user interaction
|
|
||||||
fireEvent(element, eventType, options = {}) {
|
|
||||||
const event = new Event(eventType, {
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: true,
|
|
||||||
...options
|
|
||||||
});
|
|
||||||
|
|
||||||
element.dispatchEvent(event);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Wait for async operations
|
|
||||||
waitFor(condition, timeout = 5000) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
const check = () => {
|
|
||||||
if (condition()) {
|
|
||||||
resolve();
|
|
||||||
} else if (Date.now() - startTime > timeout) {
|
|
||||||
reject(new Error('Timeout waiting for condition'));
|
|
||||||
} else {
|
|
||||||
setTimeout(check, 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
check();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
// Clean up test DOM
|
|
||||||
cleanup() {
|
|
||||||
document.querySelectorAll('[data-testid]').forEach(el => {
|
|
||||||
el.remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
This architecture ensures consistent, maintainable, and performant templates across all agents while providing the flexibility to customize specific features as needed.
|
|
||||||
@ -1,379 +0,0 @@
|
|||||||
# Template Comparison Framework
|
|
||||||
|
|
||||||
A systematic approach to ensure pixel-perfect template matching and prevent implementation failures.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This framework provides a comprehensive methodology for comparing, analyzing, and implementing template changes with 100% accuracy. It prevents the issues that occurred with the Social Ads Generator initial implementation.
|
|
||||||
|
|
||||||
## Phase 1: Complete Template Analysis
|
|
||||||
|
|
||||||
### 1.1 Source Template Deep Dive
|
|
||||||
|
|
||||||
**Pre-Analysis Checklist:**
|
|
||||||
- [ ] Read entire source template (100% coverage)
|
|
||||||
- [ ] Identify all CSS classes and their purposes
|
|
||||||
- [ ] Map all JavaScript functions and their behaviors
|
|
||||||
- [ ] Document all HTML structure patterns
|
|
||||||
- [ ] Note all responsive design breakpoints
|
|
||||||
- [ ] Catalog all interactive elements
|
|
||||||
|
|
||||||
**Template Structure Mapping:**
|
|
||||||
```
|
|
||||||
Source Template: [Template Name]
|
|
||||||
├── Header Structure
|
|
||||||
│ ├── Title/Subtitle elements
|
|
||||||
│ ├── Control elements (buttons, widgets)
|
|
||||||
│ └── Layout positioning
|
|
||||||
├── Main Content Area
|
|
||||||
│ ├── Form structure
|
|
||||||
│ ├── Widget layout
|
|
||||||
│ └── Content organization
|
|
||||||
├── Sidebar/Secondary Content
|
|
||||||
│ ├── Widget composition
|
|
||||||
│ ├── Interactive elements
|
|
||||||
│ └── Quick access features
|
|
||||||
└── Footer/Additional Elements
|
|
||||||
├── Processing status
|
|
||||||
├── Results display
|
|
||||||
└── Action buttons
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.2 CSS Architecture Analysis
|
|
||||||
|
|
||||||
**Design Token Inventory:**
|
|
||||||
- [ ] Color variables (`--primary`, `--surface`, etc.)
|
|
||||||
- [ ] Spacing variables (`--spacing-xs` to `--spacing-xl`)
|
|
||||||
- [ ] Typography variables (`--font-size-*`, `--font-weight-*`)
|
|
||||||
- [ ] Border radius variables (`--radius-*`)
|
|
||||||
- [ ] Shadow variables (`--shadow-*`)
|
|
||||||
- [ ] Transition variables (`--transition-*`)
|
|
||||||
|
|
||||||
**Component Class Mapping:**
|
|
||||||
```css
|
|
||||||
/* Component Analysis Template */
|
|
||||||
.component-name {
|
|
||||||
/* Base Properties */
|
|
||||||
background: var(--token-name);
|
|
||||||
border: specification;
|
|
||||||
padding: spacing-value;
|
|
||||||
margin: spacing-value;
|
|
||||||
|
|
||||||
/* Layout Properties */
|
|
||||||
display: layout-type;
|
|
||||||
flex/grid: properties;
|
|
||||||
position: positioning;
|
|
||||||
|
|
||||||
/* Visual Properties */
|
|
||||||
color: color-value;
|
|
||||||
font-size: typography-value;
|
|
||||||
box-shadow: shadow-value;
|
|
||||||
|
|
||||||
/* Interaction Properties */
|
|
||||||
transition: transition-value;
|
|
||||||
cursor: cursor-type;
|
|
||||||
}
|
|
||||||
|
|
||||||
.component-name:hover {
|
|
||||||
/* Hover state changes */
|
|
||||||
}
|
|
||||||
|
|
||||||
.component-name.active {
|
|
||||||
/* Active state changes */
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.3 JavaScript Function Analysis
|
|
||||||
|
|
||||||
**Function Mapping Template:**
|
|
||||||
```javascript
|
|
||||||
// Function Analysis Template
|
|
||||||
Function Name: [functionName]
|
|
||||||
Purpose: [What it does]
|
|
||||||
Parameters: [Input parameters]
|
|
||||||
Return Value: [What it returns]
|
|
||||||
Dependencies: [Other functions it calls]
|
|
||||||
DOM Elements: [Elements it manipulates]
|
|
||||||
Event Listeners: [Events it responds to]
|
|
||||||
Side Effects: [Changes it makes to the system]
|
|
||||||
|
|
||||||
// Example:
|
|
||||||
Function Name: toggleQuickAgents
|
|
||||||
Purpose: Opens/closes the quick agents panel
|
|
||||||
Parameters: None
|
|
||||||
Return Value: None
|
|
||||||
Dependencies: closeQuickAgents
|
|
||||||
DOM Elements: quickAgentsPanel, quickAgentsOverlay, quick-agent-toggle
|
|
||||||
Event Listeners: click events
|
|
||||||
Side Effects: Modifies CSS classes, ARIA attributes, body overflow
|
|
||||||
```
|
|
||||||
|
|
||||||
## Phase 2: Target Template Assessment
|
|
||||||
|
|
||||||
### 2.1 Current State Analysis
|
|
||||||
|
|
||||||
**Existing Structure Inventory:**
|
|
||||||
- [ ] Document current layout structure
|
|
||||||
- [ ] Identify existing CSS classes
|
|
||||||
- [ ] Map current JavaScript functions
|
|
||||||
- [ ] Note current styling approach
|
|
||||||
- [ ] Identify existing interactive elements
|
|
||||||
|
|
||||||
### 2.2 Gap Analysis
|
|
||||||
|
|
||||||
**Component Comparison Table:**
|
|
||||||
| Component | Source Template | Target Template | Status | Action Required |
|
|
||||||
|-----------|-----------------|-----------------|--------|-----------------|
|
|
||||||
| Header Layout | `agent-header` with title + controls | Current structure | ❌ Different | Update structure |
|
|
||||||
| Wallet Card | Gradient bg, white text | Current styling | ❌ Different | Replace CSS |
|
|
||||||
| Quick Agent Button | Inside "How It Works" | In header | ❌ Wrong position | Move to widget |
|
|
||||||
| Panel Animation | `transform: translateX` | Current animation | ❌ Different | Update animation |
|
|
||||||
|
|
||||||
**Missing Elements Checklist:**
|
|
||||||
- [ ] Missing CSS classes: `[list]`
|
|
||||||
- [ ] Missing JavaScript functions: `[list]`
|
|
||||||
- [ ] Missing HTML structures: `[list]`
|
|
||||||
- [ ] Missing styling patterns: `[list]`
|
|
||||||
- [ ] Missing interactive behaviors: `[list]`
|
|
||||||
|
|
||||||
### 2.3 Change Impact Assessment
|
|
||||||
|
|
||||||
**Dependency Mapping:**
|
|
||||||
```
|
|
||||||
Change: Move Quick Agent Button
|
|
||||||
├── Direct Impact
|
|
||||||
│ ├── HTML structure modification
|
|
||||||
│ ├── CSS class updates
|
|
||||||
│ └── JavaScript function calls
|
|
||||||
├── Indirect Impact
|
|
||||||
│ ├── Event listener updates
|
|
||||||
│ ├── ARIA attribute changes
|
|
||||||
│ └── Responsive design adjustments
|
|
||||||
└── Risk Assessment
|
|
||||||
├── Breaking existing functionality
|
|
||||||
├── Accessibility implications
|
|
||||||
└── Cross-browser compatibility
|
|
||||||
```
|
|
||||||
|
|
||||||
## Phase 3: Implementation Planning
|
|
||||||
|
|
||||||
### 3.1 Change Prioritization
|
|
||||||
|
|
||||||
**Priority Matrix:**
|
|
||||||
1. **Critical (Must Fix)**: Layout structure mismatches
|
|
||||||
2. **High (Should Fix)**: Styling inconsistencies
|
|
||||||
3. **Medium (Good to Fix)**: Minor visual differences
|
|
||||||
4. **Low (Nice to Fix)**: Optimization opportunities
|
|
||||||
|
|
||||||
**Implementation Order:**
|
|
||||||
1. **Foundation Changes**: Base HTML structure
|
|
||||||
2. **Layout Changes**: CSS architecture updates
|
|
||||||
3. **Styling Changes**: Visual property updates
|
|
||||||
4. **Interaction Changes**: JavaScript function updates
|
|
||||||
5. **Polish Changes**: Final adjustments and optimizations
|
|
||||||
|
|
||||||
### 3.2 Risk Mitigation
|
|
||||||
|
|
||||||
**Potential Issues:**
|
|
||||||
- [ ] Breaking existing functionality
|
|
||||||
- [ ] Introducing accessibility issues
|
|
||||||
- [ ] Creating responsive design problems
|
|
||||||
- [ ] Causing JavaScript errors
|
|
||||||
- [ ] Affecting user experience
|
|
||||||
|
|
||||||
**Mitigation Strategies:**
|
|
||||||
- [ ] Test each change incrementally
|
|
||||||
- [ ] Maintain backup of original code
|
|
||||||
- [ ] Validate accessibility after changes
|
|
||||||
- [ ] Test responsive design at each breakpoint
|
|
||||||
- [ ] Verify JavaScript functionality
|
|
||||||
|
|
||||||
### 3.3 Validation Plan
|
|
||||||
|
|
||||||
**Testing Checklist:**
|
|
||||||
- [ ] Visual comparison with source template
|
|
||||||
- [ ] Functionality testing of all interactive elements
|
|
||||||
- [ ] Responsive design validation
|
|
||||||
- [ ] Accessibility compliance check
|
|
||||||
- [ ] Cross-browser compatibility test
|
|
||||||
- [ ] Performance impact assessment
|
|
||||||
|
|
||||||
## Phase 4: Implementation Execution
|
|
||||||
|
|
||||||
### 4.1 Systematic Change Process
|
|
||||||
|
|
||||||
**Step-by-Step Implementation:**
|
|
||||||
|
|
||||||
1. **Backup Original**: Create copy of current template
|
|
||||||
2. **HTML Structure**: Update layout to match source
|
|
||||||
3. **CSS Architecture**: Replace styling with source patterns
|
|
||||||
4. **JavaScript Functions**: Update behaviors to match source
|
|
||||||
5. **Validation**: Test each component individually
|
|
||||||
6. **Integration**: Ensure all components work together
|
|
||||||
7. **Final Review**: Compare with source template
|
|
||||||
|
|
||||||
### 4.2 Quality Gates
|
|
||||||
|
|
||||||
**Gate 1: Structure Validation**
|
|
||||||
- [ ] HTML structure matches source template
|
|
||||||
- [ ] All required classes are present
|
|
||||||
- [ ] Semantic HTML is maintained
|
|
||||||
- [ ] Accessibility attributes are correct
|
|
||||||
|
|
||||||
**Gate 2: Styling Validation**
|
|
||||||
- [ ] CSS matches source template exactly
|
|
||||||
- [ ] All design tokens are used correctly
|
|
||||||
- [ ] Responsive design works as expected
|
|
||||||
- [ ] Visual appearance matches source
|
|
||||||
|
|
||||||
**Gate 3: Functionality Validation**
|
|
||||||
- [ ] All JavaScript functions work correctly
|
|
||||||
- [ ] Interactive elements behave as expected
|
|
||||||
- [ ] Event listeners are properly attached
|
|
||||||
- [ ] Error handling is maintained
|
|
||||||
|
|
||||||
**Gate 4: Integration Validation**
|
|
||||||
- [ ] All components work together seamlessly
|
|
||||||
- [ ] No conflicts between different sections
|
|
||||||
- [ ] Performance is not negatively impacted
|
|
||||||
- [ ] User experience is smooth and intuitive
|
|
||||||
|
|
||||||
### 4.3 Documentation Requirements
|
|
||||||
|
|
||||||
**Change Documentation:**
|
|
||||||
- [ ] List all changes made
|
|
||||||
- [ ] Explain reasoning for each change
|
|
||||||
- [ ] Document any deviations from source
|
|
||||||
- [ ] Note any potential future issues
|
|
||||||
- [ ] Provide rollback instructions
|
|
||||||
|
|
||||||
## Phase 5: Post-Implementation Validation
|
|
||||||
|
|
||||||
### 5.1 Final Comparison
|
|
||||||
|
|
||||||
**Pixel-Perfect Validation:**
|
|
||||||
- [ ] Visual comparison using browser dev tools
|
|
||||||
- [ ] Side-by-side screenshot comparison
|
|
||||||
- [ ] Element positioning verification
|
|
||||||
- [ ] Color and typography matching
|
|
||||||
- [ ] Interactive behavior validation
|
|
||||||
|
|
||||||
### 5.2 User Acceptance Testing
|
|
||||||
|
|
||||||
**Validation Criteria:**
|
|
||||||
- [ ] All user requirements met
|
|
||||||
- [ ] Visual appearance matches expectations
|
|
||||||
- [ ] Functionality works as intended
|
|
||||||
- [ ] No regressions introduced
|
|
||||||
- [ ] Performance is acceptable
|
|
||||||
|
|
||||||
### 5.3 Knowledge Capture
|
|
||||||
|
|
||||||
**Learning Documentation:**
|
|
||||||
- [ ] What worked well in this implementation
|
|
||||||
- [ ] What challenges were encountered
|
|
||||||
- [ ] What could be improved next time
|
|
||||||
- [ ] Reusable patterns identified
|
|
||||||
- [ ] Best practices discovered
|
|
||||||
|
|
||||||
## Tools and Templates
|
|
||||||
|
|
||||||
### Template Diff Analyzer
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Template comparison script
|
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo "=== Template Comparison Analysis ==="
|
|
||||||
echo "Source: $1"
|
|
||||||
echo "Target: $2"
|
|
||||||
echo "=================================="
|
|
||||||
|
|
||||||
# HTML structure comparison
|
|
||||||
echo "HTML Structure Differences:"
|
|
||||||
diff -u $1 $2 | grep -E "^[+-].*<|^[+-].*class="
|
|
||||||
|
|
||||||
# CSS class extraction
|
|
||||||
echo "CSS Classes in Source:"
|
|
||||||
grep -oE 'class="[^"]*"' $1 | sort | uniq
|
|
||||||
|
|
||||||
echo "CSS Classes in Target:"
|
|
||||||
grep -oE 'class="[^"]*"' $2 | sort | uniq
|
|
||||||
|
|
||||||
# JavaScript function extraction
|
|
||||||
echo "JavaScript Functions in Source:"
|
|
||||||
grep -oE 'function [a-zA-Z_][a-zA-Z0-9_]*' $1
|
|
||||||
|
|
||||||
echo "JavaScript Functions in Target:"
|
|
||||||
grep -oE 'function [a-zA-Z_][a-zA-Z0-9_]*' $2
|
|
||||||
```
|
|
||||||
|
|
||||||
### Component Mapping Template
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Component Mapping: [Source] → [Target]
|
|
||||||
|
|
||||||
## Header Component
|
|
||||||
- **Source Structure**: [Description]
|
|
||||||
- **Target Structure**: [Description]
|
|
||||||
- **Changes Required**: [List]
|
|
||||||
- **CSS Classes**: [List]
|
|
||||||
- **JavaScript Functions**: [List]
|
|
||||||
|
|
||||||
## Main Content Component
|
|
||||||
- **Source Structure**: [Description]
|
|
||||||
- **Target Structure**: [Description]
|
|
||||||
- **Changes Required**: [List]
|
|
||||||
- **CSS Classes**: [List]
|
|
||||||
- **JavaScript Functions**: [List]
|
|
||||||
|
|
||||||
## Sidebar Component
|
|
||||||
- **Source Structure**: [Description]
|
|
||||||
- **Target Structure**: [Description]
|
|
||||||
- **Changes Required**: [List]
|
|
||||||
- **CSS Classes**: [List]
|
|
||||||
- **JavaScript Functions**: [List]
|
|
||||||
|
|
||||||
## Interactive Elements
|
|
||||||
- **Source Behaviors**: [List]
|
|
||||||
- **Target Behaviors**: [List]
|
|
||||||
- **Changes Required**: [List]
|
|
||||||
- **Event Listeners**: [List]
|
|
||||||
- **State Management**: [List]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Implementation Checklist
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Implementation Checklist: [Template Name]
|
|
||||||
|
|
||||||
## Pre-Implementation
|
|
||||||
- [ ] Source template completely analyzed
|
|
||||||
- [ ] Target template current state documented
|
|
||||||
- [ ] Gap analysis completed
|
|
||||||
- [ ] Change plan created
|
|
||||||
- [ ] Risk assessment completed
|
|
||||||
|
|
||||||
## Implementation
|
|
||||||
- [ ] HTML structure updated
|
|
||||||
- [ ] CSS architecture replaced
|
|
||||||
- [ ] JavaScript functions updated
|
|
||||||
- [ ] Responsive design verified
|
|
||||||
- [ ] Accessibility maintained
|
|
||||||
|
|
||||||
## Validation
|
|
||||||
- [ ] Visual comparison completed
|
|
||||||
- [ ] Functionality tested
|
|
||||||
- [ ] Performance verified
|
|
||||||
- [ ] User acceptance obtained
|
|
||||||
- [ ] Documentation updated
|
|
||||||
|
|
||||||
## Post-Implementation
|
|
||||||
- [ ] Changes documented
|
|
||||||
- [ ] Lessons learned captured
|
|
||||||
- [ ] Best practices identified
|
|
||||||
- [ ] Template patterns updated
|
|
||||||
- [ ] Knowledge base updated
|
|
||||||
```
|
|
||||||
|
|
||||||
This framework ensures that future template implementations will be systematic, thorough, and error-free, preventing the issues that occurred with the initial Social Ads Generator implementation.
|
|
||||||
@ -1,525 +0,0 @@
|
|||||||
# UI/UX Checklist for Agent Creation
|
|
||||||
|
|
||||||
Comprehensive checklist to ensure consistent, accessible, and user-friendly agent interfaces without failures.
|
|
||||||
|
|
||||||
## Pre-Development Checklist
|
|
||||||
|
|
||||||
### 1. Requirements Analysis
|
|
||||||
- [ ] Define agent purpose and target users
|
|
||||||
- [ ] Identify required input fields and types
|
|
||||||
- [ ] Determine output format and display needs
|
|
||||||
- [ ] Plan wallet integration requirements
|
|
||||||
- [ ] Define success metrics and KPIs
|
|
||||||
|
|
||||||
### 2. Design System Preparation
|
|
||||||
- [ ] Confirm design token usage (colors, spacing, typography)
|
|
||||||
- [ ] Prepare component library references
|
|
||||||
- [ ] Plan responsive breakpoints
|
|
||||||
- [ ] Define accessibility requirements
|
|
||||||
- [ ] Create wireframes or mockups
|
|
||||||
|
|
||||||
### 3. Technical Setup
|
|
||||||
- [ ] Set up development environment
|
|
||||||
- [ ] Configure linting and formatting tools
|
|
||||||
- [ ] Set up testing framework
|
|
||||||
- [ ] Configure security tools
|
|
||||||
- [ ] Prepare deployment pipeline
|
|
||||||
|
|
||||||
## Template Implementation Checklist
|
|
||||||
|
|
||||||
### 1. Base Template Structure
|
|
||||||
- [ ] Extend from `base.html` correctly
|
|
||||||
- [ ] Include proper `{% load static %}` tags
|
|
||||||
- [ ] Set appropriate page title with agent name
|
|
||||||
- [ ] Include proper meta tags for SEO
|
|
||||||
- [ ] Add structured data markup
|
|
||||||
|
|
||||||
### 2. CSS Architecture
|
|
||||||
- [ ] Use self-contained styles (no external dependencies)
|
|
||||||
- [ ] Implement CSS custom properties for theming
|
|
||||||
- [ ] Follow BEM or consistent naming convention
|
|
||||||
- [ ] Include responsive design breakpoints
|
|
||||||
- [ ] Add dark mode support (if required)
|
|
||||||
|
|
||||||
```css
|
|
||||||
/* CSS Checklist Template */
|
|
||||||
:root {
|
|
||||||
/* ✅ Design tokens defined */
|
|
||||||
--primary: #000000;
|
|
||||||
--surface: #ffffff;
|
|
||||||
/* ... other tokens */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ✅ Component base styles */
|
|
||||||
.widget {
|
|
||||||
background: var(--surface);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
/* ... */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ✅ Responsive design */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.agent-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Layout Structure
|
|
||||||
- [ ] Use semantic HTML5 elements
|
|
||||||
- [ ] Implement proper heading hierarchy (h1, h2, h3)
|
|
||||||
- [ ] Add ARIA landmarks and labels
|
|
||||||
- [ ] Include skip navigation links
|
|
||||||
- [ ] Ensure logical tab order
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!-- HTML Structure Checklist -->
|
|
||||||
<main class="agent-container" role="main">
|
|
||||||
<div class="agent-grid">
|
|
||||||
<section class="agent-main">
|
|
||||||
<header class="agent-header">
|
|
||||||
<h1>{{ agent.name }}</h1>
|
|
||||||
<p>{{ agent.description }}</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<form class="agent-form" role="form">
|
|
||||||
<!-- Form content -->
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<section class="agent-output" aria-live="polite">
|
|
||||||
<!-- Output content -->
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<aside class="agent-sidebar">
|
|
||||||
<!-- Widget content -->
|
|
||||||
</aside>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Form Implementation Checklist
|
|
||||||
|
|
||||||
### 1. Form Structure
|
|
||||||
- [ ] Include proper CSRF protection
|
|
||||||
- [ ] Add form validation attributes
|
|
||||||
- [ ] Include helpful placeholder text
|
|
||||||
- [ ] Add required field indicators
|
|
||||||
- [ ] Implement proper error handling
|
|
||||||
|
|
||||||
### 2. Input Fields
|
|
||||||
- [ ] Use appropriate input types (text, email, url, number)
|
|
||||||
- [ ] Add proper labels and associations
|
|
||||||
- [ ] Include helpful descriptions
|
|
||||||
- [ ] Set appropriate constraints (min, max, pattern)
|
|
||||||
- [ ] Add autocomplete attributes
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!-- Form Field Checklist -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="emailInput" class="form-label">
|
|
||||||
Email Address
|
|
||||||
<span class="required" aria-label="required">*</span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
id="emailInput"
|
|
||||||
name="email"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="Enter your email address"
|
|
||||||
aria-describedby="emailHelp"
|
|
||||||
required
|
|
||||||
autocomplete="email"
|
|
||||||
>
|
|
||||||
<div id="emailHelp" class="form-help">
|
|
||||||
We'll never share your email with anyone else.
|
|
||||||
</div>
|
|
||||||
<div id="emailInput-error" class="form-error" role="alert"></div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. File Upload Fields
|
|
||||||
- [ ] Implement file type validation
|
|
||||||
- [ ] Add file size restrictions
|
|
||||||
- [ ] Include drag and drop functionality
|
|
||||||
- [ ] Show upload progress
|
|
||||||
- [ ] Add file preview capabilities
|
|
||||||
|
|
||||||
### 4. Form Validation
|
|
||||||
- [ ] Implement client-side validation
|
|
||||||
- [ ] Add real-time validation feedback
|
|
||||||
- [ ] Include server-side validation
|
|
||||||
- [ ] Show clear error messages
|
|
||||||
- [ ] Prevent form submission with errors
|
|
||||||
|
|
||||||
## Widget Implementation Checklist
|
|
||||||
|
|
||||||
### 1. Wallet Widget
|
|
||||||
- [ ] Display current balance prominently
|
|
||||||
- [ ] Show currency (AED) consistently
|
|
||||||
- [ ] Include top-up call-to-action
|
|
||||||
- [ ] Add real-time balance updates
|
|
||||||
- [ ] Handle insufficient balance gracefully
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!-- Wallet Widget Checklist -->
|
|
||||||
<div class="widget wallet-widget">
|
|
||||||
<div class="widget-header">
|
|
||||||
<h3>💰 Your Wallet</h3>
|
|
||||||
</div>
|
|
||||||
<div class="widget-content">
|
|
||||||
<div class="balance-display">
|
|
||||||
<span class="balance-amount" id="walletBalance">
|
|
||||||
{{ user.wallet_balance|floatformat:2 }}
|
|
||||||
</span>
|
|
||||||
<span class="balance-currency">AED</span>
|
|
||||||
</div>
|
|
||||||
<a href="{% url 'wallet_topup' %}" class="btn btn-primary btn-sm">
|
|
||||||
Top Up Wallet
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. How It Works Widget
|
|
||||||
- [ ] Provide clear step-by-step instructions
|
|
||||||
- [ ] Use numbered or bulleted lists
|
|
||||||
- [ ] Include relevant icons or visuals
|
|
||||||
- [ ] Keep instructions concise
|
|
||||||
- [ ] Add helpful tips or warnings
|
|
||||||
|
|
||||||
### 3. Other Agents Widget
|
|
||||||
- [ ] Implement quick agent access panel
|
|
||||||
- [ ] Include agent descriptions
|
|
||||||
- [ ] Add proper navigation links
|
|
||||||
- [ ] Show relevant agent suggestions
|
|
||||||
- [ ] Include smooth animations
|
|
||||||
|
|
||||||
## JavaScript Implementation Checklist
|
|
||||||
|
|
||||||
### 1. Security Implementation
|
|
||||||
- [ ] Implement HTML sanitization functions
|
|
||||||
- [ ] Add XSS prevention measures
|
|
||||||
- [ ] Validate all user inputs
|
|
||||||
- [ ] Use secure content insertion methods
|
|
||||||
- [ ] Implement CSRF protection
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Security Checklist Example
|
|
||||||
function safeSetHTML(element, content) {
|
|
||||||
// ✅ Sanitize HTML content
|
|
||||||
const sanitized = HTMLSanitizer.sanitize(content);
|
|
||||||
element.innerHTML = sanitized;
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateInput(input) {
|
|
||||||
// ✅ Validate and sanitize input
|
|
||||||
return InputValidator.validateText(input, {
|
|
||||||
required: true,
|
|
||||||
maxLength: 1000,
|
|
||||||
allowHTML: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Performance Optimization
|
|
||||||
- [ ] Implement debouncing for form interactions
|
|
||||||
- [ ] Add proper event listener management
|
|
||||||
- [ ] Use efficient DOM queries
|
|
||||||
- [ ] Implement lazy loading where appropriate
|
|
||||||
- [ ] Add loading states and feedback
|
|
||||||
|
|
||||||
### 3. State Management
|
|
||||||
- [ ] Implement proper state management
|
|
||||||
- [ ] Handle loading states
|
|
||||||
- [ ] Manage error states
|
|
||||||
- [ ] Update UI based on state changes
|
|
||||||
- [ ] Persist important state data
|
|
||||||
|
|
||||||
### 4. Wallet Integration
|
|
||||||
- [ ] Implement dynamic balance validation
|
|
||||||
- [ ] Add real-time balance updates
|
|
||||||
- [ ] Handle insufficient balance scenarios
|
|
||||||
- [ ] Sync header and page balance displays
|
|
||||||
- [ ] Add proper error handling
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Wallet Integration Checklist
|
|
||||||
function validateWalletBalance() {
|
|
||||||
// ✅ Read balance dynamically from DOM
|
|
||||||
const balanceElement = document.getElementById('walletBalance');
|
|
||||||
const currentBalance = parseFloat(balanceElement.textContent) || 0;
|
|
||||||
|
|
||||||
if (currentBalance < 4.00) {
|
|
||||||
showError('Insufficient wallet balance. Please top up your wallet.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateWalletBalance(newBalance) {
|
|
||||||
// ✅ Update both header and page balance
|
|
||||||
const headerBalance = document.querySelector('a[data-wallet-balance]');
|
|
||||||
const pageBalance = document.getElementById('walletBalance');
|
|
||||||
|
|
||||||
if (headerBalance) {
|
|
||||||
headerBalance.textContent = `💰 ${newBalance.toFixed(2)} AED`;
|
|
||||||
}
|
|
||||||
if (pageBalance) {
|
|
||||||
pageBalance.textContent = newBalance.toFixed(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Accessibility Checklist
|
|
||||||
|
|
||||||
### 1. Keyboard Navigation
|
|
||||||
- [ ] All interactive elements are keyboard accessible
|
|
||||||
- [ ] Proper tab order is maintained
|
|
||||||
- [ ] Focus indicators are visible
|
|
||||||
- [ ] Escape key closes modals/panels
|
|
||||||
- [ ] Arrow keys work for navigation where appropriate
|
|
||||||
|
|
||||||
### 2. Screen Reader Support
|
|
||||||
- [ ] All images have appropriate alt text
|
|
||||||
- [ ] Form fields have proper labels
|
|
||||||
- [ ] ARIA attributes are used correctly
|
|
||||||
- [ ] Live regions for dynamic content
|
|
||||||
- [ ] Proper heading structure
|
|
||||||
|
|
||||||
### 3. Color and Contrast
|
|
||||||
- [ ] Sufficient color contrast ratios
|
|
||||||
- [ ] Information not conveyed by color alone
|
|
||||||
- [ ] Focus indicators are visible
|
|
||||||
- [ ] Error states are clearly indicated
|
|
||||||
- [ ] Dark mode support (if required)
|
|
||||||
|
|
||||||
### 4. Content Accessibility
|
|
||||||
- [ ] Clear and simple language
|
|
||||||
- [ ] Proper font sizes and line heights
|
|
||||||
- [ ] Sufficient spacing between elements
|
|
||||||
- [ ] Responsive text sizing
|
|
||||||
- [ ] Alternative text for complex information
|
|
||||||
|
|
||||||
## Responsive Design Checklist
|
|
||||||
|
|
||||||
### 1. Mobile First Approach
|
|
||||||
- [ ] Design works on 320px width
|
|
||||||
- [ ] Touch targets are at least 44px
|
|
||||||
- [ ] Text is readable without zooming
|
|
||||||
- [ ] Navigation is mobile-friendly
|
|
||||||
- [ ] Forms work well on mobile
|
|
||||||
|
|
||||||
### 2. Breakpoint Implementation
|
|
||||||
- [ ] Mobile: 0-767px
|
|
||||||
- [ ] Tablet: 768-1023px
|
|
||||||
- [ ] Desktop: 1024px+
|
|
||||||
- [ ] Large desktop: 1200px+
|
|
||||||
- [ ] Test all breakpoints
|
|
||||||
|
|
||||||
### 3. Layout Adaptation
|
|
||||||
- [ ] Grid system adapts properly
|
|
||||||
- [ ] Sidebar moves to appropriate position
|
|
||||||
- [ ] Typography scales appropriately
|
|
||||||
- [ ] Images and media are responsive
|
|
||||||
- [ ] Navigation adapts to screen size
|
|
||||||
|
|
||||||
## Performance Checklist
|
|
||||||
|
|
||||||
### 1. Loading Performance
|
|
||||||
- [ ] Optimize images and assets
|
|
||||||
- [ ] Minimize HTTP requests
|
|
||||||
- [ ] Use efficient CSS and JavaScript
|
|
||||||
- [ ] Implement caching strategies
|
|
||||||
- [ ] Add loading indicators
|
|
||||||
|
|
||||||
### 2. Runtime Performance
|
|
||||||
- [ ] Efficient DOM manipulation
|
|
||||||
- [ ] Debounced event handlers
|
|
||||||
- [ ] Minimal memory leaks
|
|
||||||
- [ ] Smooth animations
|
|
||||||
- [ ] Responsive user interactions
|
|
||||||
|
|
||||||
### 3. Network Performance
|
|
||||||
- [ ] Optimize API calls
|
|
||||||
- [ ] Implement request batching
|
|
||||||
- [ ] Add proper error handling
|
|
||||||
- [ ] Use appropriate timeouts
|
|
||||||
- [ ] Implement retry logic
|
|
||||||
|
|
||||||
## Testing Checklist
|
|
||||||
|
|
||||||
### 1. Functional Testing
|
|
||||||
- [ ] All form submissions work correctly
|
|
||||||
- [ ] File uploads function properly
|
|
||||||
- [ ] Wallet balance updates correctly
|
|
||||||
- [ ] Error handling works as expected
|
|
||||||
- [ ] Navigation functions properly
|
|
||||||
|
|
||||||
### 2. Browser Testing
|
|
||||||
- [ ] Chrome (latest)
|
|
||||||
- [ ] Firefox (latest)
|
|
||||||
- [ ] Safari (latest)
|
|
||||||
- [ ] Edge (latest)
|
|
||||||
- [ ] Mobile browsers
|
|
||||||
|
|
||||||
### 3. Device Testing
|
|
||||||
- [ ] Desktop (1920x1080)
|
|
||||||
- [ ] Laptop (1366x768)
|
|
||||||
- [ ] Tablet (768x1024)
|
|
||||||
- [ ] Mobile (375x667)
|
|
||||||
- [ ] Large mobile (414x896)
|
|
||||||
|
|
||||||
### 4. Accessibility Testing
|
|
||||||
- [ ] Screen reader testing
|
|
||||||
- [ ] Keyboard navigation testing
|
|
||||||
- [ ] Color contrast validation
|
|
||||||
- [ ] ARIA validation
|
|
||||||
- [ ] Automated accessibility testing
|
|
||||||
|
|
||||||
## Security Testing Checklist
|
|
||||||
|
|
||||||
### 1. Input Validation
|
|
||||||
- [ ] XSS protection testing
|
|
||||||
- [ ] SQL injection prevention
|
|
||||||
- [ ] File upload security
|
|
||||||
- [ ] CSRF protection
|
|
||||||
- [ ] Input sanitization
|
|
||||||
|
|
||||||
### 2. Authentication & Authorization
|
|
||||||
- [ ] Proper session management
|
|
||||||
- [ ] Access control verification
|
|
||||||
- [ ] Token validation
|
|
||||||
- [ ] Permission checking
|
|
||||||
- [ ] Rate limiting
|
|
||||||
|
|
||||||
### 3. Data Protection
|
|
||||||
- [ ] Sensitive data encryption
|
|
||||||
- [ ] Secure data transmission
|
|
||||||
- [ ] Proper error handling
|
|
||||||
- [ ] Information disclosure prevention
|
|
||||||
- [ ] Audit logging
|
|
||||||
|
|
||||||
## Deployment Checklist
|
|
||||||
|
|
||||||
### 1. Pre-Deployment
|
|
||||||
- [ ] All tests pass
|
|
||||||
- [ ] Code review completed
|
|
||||||
- [ ] Security scan completed
|
|
||||||
- [ ] Performance testing done
|
|
||||||
- [ ] Documentation updated
|
|
||||||
|
|
||||||
### 2. Deployment Process
|
|
||||||
- [ ] Database migrations run
|
|
||||||
- [ ] Static files collected
|
|
||||||
- [ ] Environment variables set
|
|
||||||
- [ ] SSL certificates valid
|
|
||||||
- [ ] Monitoring configured
|
|
||||||
|
|
||||||
### 3. Post-Deployment
|
|
||||||
- [ ] Functionality verification
|
|
||||||
- [ ] Performance monitoring
|
|
||||||
- [ ] Error tracking active
|
|
||||||
- [ ] User feedback collection
|
|
||||||
- [ ] Analytics tracking
|
|
||||||
|
|
||||||
## Maintenance Checklist
|
|
||||||
|
|
||||||
### 1. Regular Updates
|
|
||||||
- [ ] Security patches applied
|
|
||||||
- [ ] Dependencies updated
|
|
||||||
- [ ] Browser compatibility checked
|
|
||||||
- [ ] Performance optimized
|
|
||||||
- [ ] Documentation maintained
|
|
||||||
|
|
||||||
### 2. Monitoring
|
|
||||||
- [ ] Error rates monitored
|
|
||||||
- [ ] Performance metrics tracked
|
|
||||||
- [ ] User behavior analyzed
|
|
||||||
- [ ] Security events logged
|
|
||||||
- [ ] Accessibility compliance verified
|
|
||||||
|
|
||||||
### 3. User Feedback
|
|
||||||
- [ ] User feedback collected
|
|
||||||
- [ ] Issues prioritized
|
|
||||||
- [ ] Improvements planned
|
|
||||||
- [ ] Changes communicated
|
|
||||||
- [ ] Success metrics tracked
|
|
||||||
|
|
||||||
## Quality Assurance Checklist
|
|
||||||
|
|
||||||
### 1. Code Quality
|
|
||||||
- [ ] Code follows style guidelines
|
|
||||||
- [ ] Proper commenting and documentation
|
|
||||||
- [ ] No console errors or warnings
|
|
||||||
- [ ] Efficient algorithms used
|
|
||||||
- [ ] Proper error handling
|
|
||||||
|
|
||||||
### 2. User Experience
|
|
||||||
- [ ] Intuitive user interface
|
|
||||||
- [ ] Clear user feedback
|
|
||||||
- [ ] Consistent design patterns
|
|
||||||
- [ ] Smooth interactions
|
|
||||||
- [ ] Helpful error messages
|
|
||||||
|
|
||||||
### 3. Performance Standards
|
|
||||||
- [ ] Page load time < 3 seconds
|
|
||||||
- [ ] Interactive elements responsive
|
|
||||||
- [ ] Smooth animations (60fps)
|
|
||||||
- [ ] Efficient resource usage
|
|
||||||
- [ ] Minimal JavaScript errors
|
|
||||||
|
|
||||||
## Final Review Checklist
|
|
||||||
|
|
||||||
### 1. Complete Functionality
|
|
||||||
- [ ] All requirements implemented
|
|
||||||
- [ ] Edge cases handled
|
|
||||||
- [ ] Error scenarios covered
|
|
||||||
- [ ] Performance optimized
|
|
||||||
- [ ] Security implemented
|
|
||||||
|
|
||||||
### 2. User Experience
|
|
||||||
- [ ] Intuitive navigation
|
|
||||||
- [ ] Clear instructions
|
|
||||||
- [ ] Helpful feedback
|
|
||||||
- [ ] Accessible design
|
|
||||||
- [ ] Responsive layout
|
|
||||||
|
|
||||||
### 3. Technical Excellence
|
|
||||||
- [ ] Clean, maintainable code
|
|
||||||
- [ ] Proper documentation
|
|
||||||
- [ ] Comprehensive testing
|
|
||||||
- [ ] Security best practices
|
|
||||||
- [ ] Performance optimization
|
|
||||||
|
|
||||||
### 4. Compliance
|
|
||||||
- [ ] Accessibility standards met
|
|
||||||
- [ ] Security requirements satisfied
|
|
||||||
- [ ] Performance benchmarks achieved
|
|
||||||
- [ ] Browser compatibility verified
|
|
||||||
- [ ] Mobile responsiveness confirmed
|
|
||||||
|
|
||||||
## Success Metrics
|
|
||||||
|
|
||||||
### 1. Technical Metrics
|
|
||||||
- [ ] Page load time < 3 seconds
|
|
||||||
- [ ] Zero JavaScript errors
|
|
||||||
- [ ] 100% accessibility compliance
|
|
||||||
- [ ] 95%+ browser compatibility
|
|
||||||
- [ ] 99.9% uptime
|
|
||||||
|
|
||||||
### 2. User Experience Metrics
|
|
||||||
- [ ] User satisfaction score > 4.5/5
|
|
||||||
- [ ] Task completion rate > 90%
|
|
||||||
- [ ] Error rate < 5%
|
|
||||||
- [ ] Support ticket reduction
|
|
||||||
- [ ] User retention improvement
|
|
||||||
|
|
||||||
### 3. Business Metrics
|
|
||||||
- [ ] Increased user engagement
|
|
||||||
- [ ] Higher conversion rates
|
|
||||||
- [ ] Reduced support costs
|
|
||||||
- [ ] Improved user feedback
|
|
||||||
- [ ] Enhanced platform reputation
|
|
||||||
|
|
||||||
This comprehensive checklist ensures that all agents meet high standards for functionality, usability, accessibility, security, and performance while maintaining consistency across the platform.
|
|
||||||
@ -1,676 +0,0 @@
|
|||||||
# 💳 NetCop Wallet/Stripe Implementation Guide
|
|
||||||
|
|
||||||
## 🎯 Overview
|
|
||||||
This guide documents how to implement a professional wallet system with Stripe Payment Intents API in the NetCop Django project. The system provides real-time payment processing **without requiring webhooks** for basic functionality.
|
|
||||||
|
|
||||||
## ✨ Key Features
|
|
||||||
- **Professional wallet topup interface** with Stripe Elements
|
|
||||||
- **Real-time payment processing** with Payment Intents API
|
|
||||||
- **Loading states and progress indicators** for better UX
|
|
||||||
- **Webhook-free operation** for development and testing
|
|
||||||
- **AED currency support** matching NetCop pricing
|
|
||||||
- **Balance checking** before agent usage
|
|
||||||
- **Transaction history** with copy/download functionality
|
|
||||||
|
|
||||||
## 🚫 No Webhooks Required
|
|
||||||
|
|
||||||
### Why No Webhooks Needed:
|
|
||||||
- **Payment Intents API** provides immediate payment status
|
|
||||||
- **Frontend confirmation** happens in real-time after card processing
|
|
||||||
- **Direct database updates** via confirmed payment status
|
|
||||||
- **Duplicate prevention** through payment metadata checking
|
|
||||||
|
|
||||||
### Payment Flow (Webhook-Free):
|
|
||||||
1. User selects topup amount → Frontend creates Payment Intent
|
|
||||||
2. Stripe Elements processes card securely → Returns success/failure
|
|
||||||
3. Frontend confirms payment status → Backend updates wallet immediately
|
|
||||||
4. User sees updated balance → Can use agents with sufficient funds
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🏗️ Implementation Steps
|
|
||||||
|
|
||||||
### 1. Environment Configuration
|
|
||||||
|
|
||||||
Add to `.env` file:
|
|
||||||
```bash
|
|
||||||
# Stripe Configuration (No webhook secret required for basic functionality)
|
|
||||||
STRIPE_PUBLISHABLE_KEY=pk_test_your_publishable_key_here
|
|
||||||
STRIPE_SECRET_KEY=sk_test_your_secret_key_here
|
|
||||||
# STRIPE_WEBHOOK_SECRET=whsec_... (optional for production)
|
|
||||||
```
|
|
||||||
|
|
||||||
Add to `netcop_hub/settings.py`:
|
|
||||||
```python
|
|
||||||
# Stripe Configuration
|
|
||||||
STRIPE_SECRET_KEY = config('STRIPE_SECRET_KEY', default='')
|
|
||||||
STRIPE_PUBLISHABLE_KEY = config('STRIPE_PUBLISHABLE_KEY', default='')
|
|
||||||
STRIPE_WEBHOOK_SECRET = config('STRIPE_WEBHOOK_SECRET', default='')
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. User Model Enhancement
|
|
||||||
|
|
||||||
Update `authentication/models.py` to add wallet balance:
|
|
||||||
```python
|
|
||||||
from django.contrib.auth.models import AbstractUser
|
|
||||||
from django.db import models
|
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
class User(AbstractUser):
|
|
||||||
email = models.EmailField(unique=True)
|
|
||||||
wallet_balance = models.DecimalField(
|
|
||||||
max_digits=10,
|
|
||||||
decimal_places=2,
|
|
||||||
default=Decimal('0.00'),
|
|
||||||
help_text="User wallet balance in AED"
|
|
||||||
)
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
|
||||||
|
|
||||||
USERNAME_FIELD = 'email'
|
|
||||||
REQUIRED_FIELDS = ['username']
|
|
||||||
|
|
||||||
def has_sufficient_balance(self, amount):
|
|
||||||
"""Check if user has sufficient balance for a transaction"""
|
|
||||||
return self.wallet_balance >= Decimal(str(amount))
|
|
||||||
|
|
||||||
def deduct_balance(self, amount, description=""):
|
|
||||||
"""Deduct amount from wallet balance"""
|
|
||||||
if self.has_sufficient_balance(amount):
|
|
||||||
self.wallet_balance -= Decimal(str(amount))
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
# Create transaction record
|
|
||||||
from wallet.models import WalletTransaction
|
|
||||||
WalletTransaction.objects.create(
|
|
||||||
user=self,
|
|
||||||
amount=-Decimal(str(amount)),
|
|
||||||
type='agent_usage',
|
|
||||||
description=description
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def add_balance(self, amount, description=""):
|
|
||||||
"""Add amount to wallet balance"""
|
|
||||||
self.wallet_balance += Decimal(str(amount))
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
# Create transaction record
|
|
||||||
from wallet.models import WalletTransaction
|
|
||||||
WalletTransaction.objects.create(
|
|
||||||
user=self,
|
|
||||||
amount=Decimal(str(amount)),
|
|
||||||
type='top_up',
|
|
||||||
description=description
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Wallet Models
|
|
||||||
|
|
||||||
Update `wallet/models.py`:
|
|
||||||
```python
|
|
||||||
from django.db import models
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
|
|
||||||
class WalletTransaction(models.Model):
|
|
||||||
TRANSACTION_TYPES = [
|
|
||||||
('top_up', 'Top Up'),
|
|
||||||
('agent_usage', 'Agent Usage'),
|
|
||||||
('refund', 'Refund'),
|
|
||||||
]
|
|
||||||
|
|
||||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='wallet_transactions')
|
|
||||||
amount = models.DecimalField(max_digits=10, decimal_places=2)
|
|
||||||
type = models.CharField(max_length=20, choices=TRANSACTION_TYPES)
|
|
||||||
description = models.TextField()
|
|
||||||
stripe_payment_intent_id = models.CharField(max_length=200, blank=True)
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ['-created_at']
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.user.email} - {self.amount} AED ({self.type})"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Wallet Views (Payment Intents API)
|
|
||||||
|
|
||||||
Create `wallet/views.py`:
|
|
||||||
```python
|
|
||||||
import stripe
|
|
||||||
import json
|
|
||||||
from django.conf import settings
|
|
||||||
from django.shortcuts import render
|
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
from django.http import JsonResponse
|
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.utils import timezone
|
|
||||||
from .models import WalletTransaction
|
|
||||||
from decimal import Decimal
|
|
||||||
from django.contrib.auth import get_user_model
|
|
||||||
|
|
||||||
User = get_user_model()
|
|
||||||
stripe.api_key = settings.STRIPE_SECRET_KEY
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def topup(request):
|
|
||||||
"""Professional wallet topup page"""
|
|
||||||
return render(request, "wallet/topup.html", {
|
|
||||||
'stripe_publishable_key': settings.STRIPE_PUBLISHABLE_KEY,
|
|
||||||
'user_balance': request.user.wallet_balance
|
|
||||||
})
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
@csrf_exempt
|
|
||||||
def create_payment_intent(request):
|
|
||||||
"""Create Stripe Payment Intent for wallet topup"""
|
|
||||||
if request.method == "POST":
|
|
||||||
try:
|
|
||||||
data = json.loads(request.body)
|
|
||||||
amount = int(data.get("amount"))
|
|
||||||
|
|
||||||
if amount < 1:
|
|
||||||
return JsonResponse({"error": "Amount must be at least 1 AED"}, status=400)
|
|
||||||
|
|
||||||
# Create Payment Intent
|
|
||||||
intent = stripe.PaymentIntent.create(
|
|
||||||
amount=amount * 100, # Convert to fils (AED cents)
|
|
||||||
currency='aed',
|
|
||||||
metadata={
|
|
||||||
'user_id': request.user.id,
|
|
||||||
'amount': amount,
|
|
||||||
'email': request.user.email
|
|
||||||
},
|
|
||||||
description=f"NetCop wallet top-up for {request.user.email}"
|
|
||||||
)
|
|
||||||
|
|
||||||
return JsonResponse({
|
|
||||||
'client_secret': intent.client_secret,
|
|
||||||
'amount': amount
|
|
||||||
})
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return JsonResponse({"error": str(e)}, status=400)
|
|
||||||
|
|
||||||
return JsonResponse({"error": "Invalid request method"}, status=405)
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
@csrf_exempt
|
|
||||||
def confirm_payment(request):
|
|
||||||
"""Confirm payment and update wallet balance"""
|
|
||||||
if request.method == "POST":
|
|
||||||
try:
|
|
||||||
data = json.loads(request.body)
|
|
||||||
payment_intent_id = data.get("payment_intent_id")
|
|
||||||
|
|
||||||
# Retrieve payment intent from Stripe
|
|
||||||
intent = stripe.PaymentIntent.retrieve(payment_intent_id)
|
|
||||||
|
|
||||||
if intent.status == 'succeeded':
|
|
||||||
user_id = int(intent.metadata['user_id'])
|
|
||||||
amount = Decimal(intent.metadata['amount'])
|
|
||||||
|
|
||||||
# Verify this is the correct user
|
|
||||||
if user_id != request.user.id:
|
|
||||||
return JsonResponse({"error": "Unauthorized"}, status=403)
|
|
||||||
|
|
||||||
# Check for duplicate processing
|
|
||||||
existing_transaction = WalletTransaction.objects.filter(
|
|
||||||
stripe_payment_intent_id=payment_intent_id
|
|
||||||
).first()
|
|
||||||
|
|
||||||
if not existing_transaction:
|
|
||||||
# Update user balance using model method
|
|
||||||
request.user.add_balance(
|
|
||||||
amount=amount,
|
|
||||||
description=f"Wallet top-up via Stripe - {amount} AED"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Update the transaction with Stripe ID
|
|
||||||
latest_transaction = WalletTransaction.objects.filter(
|
|
||||||
user=request.user,
|
|
||||||
type='top_up',
|
|
||||||
amount=amount
|
|
||||||
).first()
|
|
||||||
if latest_transaction:
|
|
||||||
latest_transaction.stripe_payment_intent_id = payment_intent_id
|
|
||||||
latest_transaction.save()
|
|
||||||
|
|
||||||
return JsonResponse({
|
|
||||||
"success": True,
|
|
||||||
"message": f"Successfully added {amount} AED to your wallet",
|
|
||||||
"new_balance": str(request.user.wallet_balance)
|
|
||||||
})
|
|
||||||
else:
|
|
||||||
return JsonResponse({"error": "Payment not completed"}, status=400)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return JsonResponse({"error": str(e)}, status=400)
|
|
||||||
|
|
||||||
return JsonResponse({"error": "Invalid request method"}, status=405)
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def transaction_history(request):
|
|
||||||
"""View transaction history"""
|
|
||||||
transactions = request.user.wallet_transactions.all()[:50]
|
|
||||||
return render(request, "wallet/history.html", {
|
|
||||||
'transactions': transactions,
|
|
||||||
'current_balance': request.user.wallet_balance
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Wallet URLs
|
|
||||||
|
|
||||||
Create `wallet/urls.py`:
|
|
||||||
```python
|
|
||||||
from django.urls import path
|
|
||||||
from . import views
|
|
||||||
|
|
||||||
app_name = 'wallet'
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path('', views.topup, name='topup'),
|
|
||||||
path('create-payment-intent/', views.create_payment_intent, name='create_payment_intent'),
|
|
||||||
path('confirm-payment/', views.confirm_payment, name='confirm_payment'),
|
|
||||||
path('history/', views.transaction_history, name='history'),
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. Professional Topup Template
|
|
||||||
|
|
||||||
Create `templates/wallet/topup.html`:
|
|
||||||
```html
|
|
||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block title %}Top Up Wallet - NetCop Hub{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div style="max-width: 600px; margin: var(--space-xl) auto; padding: 0 var(--space-md);">
|
|
||||||
<div class="card">
|
|
||||||
<div style="text-align: center; margin-bottom: var(--space-xl);">
|
|
||||||
<h1 style="color: var(--text-primary); margin-bottom: var(--space-sm);">💰 Top Up Wallet</h1>
|
|
||||||
<p style="color: var(--text-secondary);">Add funds to your wallet to use AI agents</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="background: var(--bg-accent); padding: var(--space-md); border-radius: var(--radius); margin-bottom: var(--space-lg); text-align: center;">
|
|
||||||
<p style="color: var(--text-secondary); margin-bottom: var(--space-xs);">Current Balance</p>
|
|
||||||
<p style="font-size: var(--text-xl); font-weight: 600; color: var(--success-green);">
|
|
||||||
{{ user_balance|floatformat:2 }} AED
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Amount Selection -->
|
|
||||||
<div style="margin-bottom: var(--space-lg);">
|
|
||||||
<label style="display: block; font-weight: 500; color: var(--text-primary); margin-bottom: var(--space-sm);">Select Amount (AED)</label>
|
|
||||||
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-sm); margin-bottom: var(--space-md);">
|
|
||||||
<button type="button" class="btn btn-secondary amount-btn" data-amount="50">50 AED</button>
|
|
||||||
<button type="button" class="btn btn-secondary amount-btn" data-amount="100">100 AED</button>
|
|
||||||
<button type="button" class="btn btn-secondary amount-btn" data-amount="200">200 AED</button>
|
|
||||||
</div>
|
|
||||||
<input type="number"
|
|
||||||
id="amount"
|
|
||||||
placeholder="Enter custom amount"
|
|
||||||
class="form-input"
|
|
||||||
style="width: 100%;"
|
|
||||||
min="1"
|
|
||||||
required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Payment Form -->
|
|
||||||
<form id="payment-form">
|
|
||||||
<div style="margin-bottom: var(--space-lg);">
|
|
||||||
<label style="display: block; font-weight: 500; color: var(--text-primary); margin-bottom: var(--space-sm);">💳 Card Information</label>
|
|
||||||
<div id="card-element" style="border: 1px solid var(--border-color); border-radius: var(--radius); padding: var(--space-md); background: var(--bg-primary);">
|
|
||||||
<!-- Stripe Elements will create form elements here -->
|
|
||||||
</div>
|
|
||||||
<div id="card-errors" style="color: var(--error-red); font-size: var(--text-sm); margin-top: var(--space-sm);" role="alert"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button id="submit-payment"
|
|
||||||
type="submit"
|
|
||||||
class="btn btn-primary"
|
|
||||||
style="width: 100%; font-size: var(--text-base);">
|
|
||||||
<span id="button-text">🚀 Add to Wallet</span>
|
|
||||||
<div id="spinner" style="display: none;">
|
|
||||||
<span style="display: inline-block; width: 16px; height: 16px; border: 2px solid #ffffff; border-radius: 50%; border-top-color: transparent; animation: spin 1s linear infinite; margin-right: var(--space-xs);"></span>
|
|
||||||
Processing...
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- Success/Error Messages -->
|
|
||||||
<div id="payment-result" style="margin-top: var(--space-lg); display: none;">
|
|
||||||
<div id="success-message" style="background: #f0fdf4; border: 1px solid #bbf7d0; color: #166534; padding: var(--space-md); border-radius: var(--radius); display: none;">
|
|
||||||
<strong>✅ Success!</strong> <span id="success-text"></span>
|
|
||||||
</div>
|
|
||||||
<div id="error-message" style="background: #fef2f2; border: 1px solid #fecaca; color: #dc2626; padding: var(--space-md); border-radius: var(--radius); display: none;">
|
|
||||||
<strong>❌ Error:</strong> <span id="error-text"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Stripe.js -->
|
|
||||||
<script src="https://js.stripe.com/v3/"></script>
|
|
||||||
<script>
|
|
||||||
// CSS for spinner animation
|
|
||||||
const style = document.createElement('style');
|
|
||||||
style.textContent = `
|
|
||||||
@keyframes spin {
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
document.head.appendChild(style);
|
|
||||||
|
|
||||||
// Initialize Stripe
|
|
||||||
const stripe = Stripe('{{ stripe_publishable_key }}');
|
|
||||||
const elements = stripe.elements();
|
|
||||||
|
|
||||||
// Create card element
|
|
||||||
const cardElement = elements.create('card', {
|
|
||||||
style: {
|
|
||||||
base: {
|
|
||||||
fontSize: '16px',
|
|
||||||
color: '#1f2937',
|
|
||||||
fontFamily: 'Inter, system-ui, sans-serif',
|
|
||||||
'::placeholder': {
|
|
||||||
color: '#9ca3af',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
invalid: {
|
|
||||||
color: '#dc2626',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
cardElement.mount('#card-element');
|
|
||||||
|
|
||||||
// Handle real-time validation errors
|
|
||||||
cardElement.addEventListener('change', ({error}) => {
|
|
||||||
const displayError = document.getElementById('card-errors');
|
|
||||||
if (error) {
|
|
||||||
displayError.textContent = error.message;
|
|
||||||
} else {
|
|
||||||
displayError.textContent = '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Amount selection buttons
|
|
||||||
document.querySelectorAll('.amount-btn').forEach(btn => {
|
|
||||||
btn.addEventListener('click', function() {
|
|
||||||
const amount = this.dataset.amount;
|
|
||||||
document.getElementById('amount').value = amount;
|
|
||||||
|
|
||||||
// Update button styles
|
|
||||||
document.querySelectorAll('.amount-btn').forEach(b => {
|
|
||||||
b.classList.remove('btn-primary');
|
|
||||||
b.classList.add('btn-secondary');
|
|
||||||
});
|
|
||||||
this.classList.remove('btn-secondary');
|
|
||||||
this.classList.add('btn-primary');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Payment form submission
|
|
||||||
const form = document.getElementById('payment-form');
|
|
||||||
form.addEventListener('submit', async (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
const amount = parseInt(document.getElementById('amount').value);
|
|
||||||
if (!amount || amount < 1) {
|
|
||||||
showError('Please enter a valid amount');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Create Payment Intent
|
|
||||||
const response = await fetch('/wallet/create-payment-intent/', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ amount: amount }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const { client_secret, error } = await response.json();
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
showError(error);
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Confirm payment with Stripe
|
|
||||||
const { error: stripeError, paymentIntent } = await stripe.confirmCardPayment(client_secret, {
|
|
||||||
payment_method: {
|
|
||||||
card: cardElement,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (stripeError) {
|
|
||||||
showError(stripeError.message);
|
|
||||||
setLoading(false);
|
|
||||||
} else if (paymentIntent.status === 'succeeded') {
|
|
||||||
// Confirm payment on server
|
|
||||||
const confirmResponse = await fetch('/wallet/confirm-payment/', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ payment_intent_id: paymentIntent.id }),
|
|
||||||
});
|
|
||||||
|
|
||||||
const confirmResult = await confirmResponse.json();
|
|
||||||
|
|
||||||
if (confirmResult.success) {
|
|
||||||
showSuccess(`${confirmResult.message}. New balance: ${confirmResult.new_balance} AED`);
|
|
||||||
// Reset form
|
|
||||||
form.reset();
|
|
||||||
cardElement.clear();
|
|
||||||
document.getElementById('amount').value = '';
|
|
||||||
// Reset amount buttons
|
|
||||||
document.querySelectorAll('.amount-btn').forEach(b => {
|
|
||||||
b.classList.remove('btn-primary');
|
|
||||||
b.classList.add('btn-secondary');
|
|
||||||
});
|
|
||||||
// Reload page after 2 seconds to show updated balance
|
|
||||||
setTimeout(() => window.location.reload(), 2000);
|
|
||||||
} else {
|
|
||||||
showError(confirmResult.error || 'Payment confirmation failed');
|
|
||||||
}
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showError('Network error: ' + error.message);
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function setLoading(loading) {
|
|
||||||
const button = document.getElementById('submit-payment');
|
|
||||||
const buttonText = document.getElementById('button-text');
|
|
||||||
const spinner = document.getElementById('spinner');
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
button.disabled = true;
|
|
||||||
buttonText.style.display = 'none';
|
|
||||||
spinner.style.display = 'inline-block';
|
|
||||||
} else {
|
|
||||||
button.disabled = false;
|
|
||||||
buttonText.style.display = 'inline-block';
|
|
||||||
spinner.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showSuccess(message) {
|
|
||||||
const resultDiv = document.getElementById('payment-result');
|
|
||||||
const successDiv = document.getElementById('success-message');
|
|
||||||
const errorDiv = document.getElementById('error-message');
|
|
||||||
const successText = document.getElementById('success-text');
|
|
||||||
|
|
||||||
successText.textContent = message;
|
|
||||||
successDiv.style.display = 'block';
|
|
||||||
errorDiv.style.display = 'none';
|
|
||||||
resultDiv.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function showError(message) {
|
|
||||||
const resultDiv = document.getElementById('payment-result');
|
|
||||||
const successDiv = document.getElementById('success-message');
|
|
||||||
const errorDiv = document.getElementById('error-message');
|
|
||||||
const errorText = document.getElementById('error-text');
|
|
||||||
|
|
||||||
errorText.textContent = message;
|
|
||||||
errorDiv.style.display = 'block';
|
|
||||||
successDiv.style.display = 'none';
|
|
||||||
resultDiv.style.display = 'block';
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 7. Update Navigation
|
|
||||||
|
|
||||||
Update `templates/base.html` to include wallet balance in navigation:
|
|
||||||
```html
|
|
||||||
<!-- In the user info section -->
|
|
||||||
{% if user.is_authenticated %}
|
|
||||||
<p class="user-welcome">Welcome, {{ user.username }}!</p>
|
|
||||||
<a href="{% url 'wallet:topup' %}" class="balance" data-wallet-balance>💰 {{ user.wallet_balance|floatformat:2 }} AED</a>
|
|
||||||
<div class="auth-links">
|
|
||||||
<a href="{% url 'wallet:topup' %}">Wallet</a>
|
|
||||||
<a href="{% url 'authentication:logout' %}">Logout</a>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 8. Update Main URLs
|
|
||||||
|
|
||||||
Add wallet URLs to `netcop_hub/urls.py`:
|
|
||||||
```python
|
|
||||||
urlpatterns = [
|
|
||||||
path('admin/', admin.site.urls),
|
|
||||||
path('auth/', include('authentication.urls')),
|
|
||||||
path('wallet/', include('wallet.urls')), # Add this line
|
|
||||||
# ... other URLs
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 Testing Guide
|
|
||||||
|
|
||||||
### 1. Database Migration
|
|
||||||
```bash
|
|
||||||
python manage.py makemigrations
|
|
||||||
python manage.py migrate
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Test with Stripe Test Cards
|
|
||||||
- **Successful payment**: `4242 4242 4242 4242`
|
|
||||||
- **Requires authentication**: `4000 0025 0000 3155`
|
|
||||||
- **Declined card**: `4000 0000 0000 9995`
|
|
||||||
|
|
||||||
### 3. Testing Checklist
|
|
||||||
- [ ] User can access wallet topup page
|
|
||||||
- [ ] Amount selection buttons work
|
|
||||||
- [ ] Card form validates properly
|
|
||||||
- [ ] Payment processing shows loading states
|
|
||||||
- [ ] Successful payments update balance immediately
|
|
||||||
- [ ] Failed payments show error messages
|
|
||||||
- [ ] Balance displays in navigation
|
|
||||||
- [ ] Transaction history is recorded
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Advanced Features (Optional)
|
|
||||||
|
|
||||||
### Agent Integration
|
|
||||||
Update agent views to check wallet balance:
|
|
||||||
```python
|
|
||||||
@login_required
|
|
||||||
def use_agent(request, agent_slug):
|
|
||||||
agent = get_object_or_404(BaseAgent, slug=agent_slug)
|
|
||||||
|
|
||||||
if not request.user.has_sufficient_balance(agent.price):
|
|
||||||
return JsonResponse({
|
|
||||||
'error': f'Insufficient balance. Need {agent.price} AED.',
|
|
||||||
'redirect_url': reverse('wallet:topup')
|
|
||||||
}, status=400)
|
|
||||||
|
|
||||||
# Deduct balance before processing
|
|
||||||
request.user.deduct_balance(
|
|
||||||
amount=agent.price,
|
|
||||||
description=f"Used {agent.name} agent"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Process agent request...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Transaction History Page
|
|
||||||
Create `templates/wallet/history.html`:
|
|
||||||
```html
|
|
||||||
{% extends 'base.html' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="card">
|
|
||||||
<h2>Transaction History</h2>
|
|
||||||
<p>Current Balance: <strong>{{ current_balance }} AED</strong></p>
|
|
||||||
|
|
||||||
<div class="transaction-list">
|
|
||||||
{% for transaction in transactions %}
|
|
||||||
<div class="transaction-item">
|
|
||||||
<span class="amount">{{ transaction.amount }} AED</span>
|
|
||||||
<span class="type">{{ transaction.get_type_display }}</span>
|
|
||||||
<span class="date">{{ transaction.created_at|date:"M d, Y H:i" }}</span>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues:
|
|
||||||
1. **Stripe keys not working**: Verify test keys are correct in `.env`
|
|
||||||
2. **Payment not confirming**: Check browser console for JavaScript errors
|
|
||||||
3. **Balance not updating**: Ensure user model has wallet_balance field
|
|
||||||
4. **CSS not loading**: Run `python manage.py collectstatic`
|
|
||||||
|
|
||||||
### Debug Mode:
|
|
||||||
Add to views.py for debugging:
|
|
||||||
```python
|
|
||||||
import logging
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
# In payment views:
|
|
||||||
logger.info(f"Payment Intent created: {intent.id}")
|
|
||||||
logger.info(f"User {request.user.id} balance updated: {request.user.wallet_balance}")
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Production Checklist
|
|
||||||
|
|
||||||
Before deploying to production:
|
|
||||||
- [ ] Switch to live Stripe keys
|
|
||||||
- [ ] Set up webhook endpoints (optional but recommended)
|
|
||||||
- [ ] Enable HTTPS for secure payments
|
|
||||||
- [ ] Set DEBUG=False in settings
|
|
||||||
- [ ] Configure proper error logging
|
|
||||||
- [ ] Test with real payment amounts
|
|
||||||
- [ ] Set up monitoring for failed payments
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
This implementation provides a complete, professional wallet system with Stripe integration that works without webhooks for development and testing, while being easily extensible for production use.
|
|
||||||
@ -1,320 +0,0 @@
|
|||||||
# Agent Polling System Guide
|
|
||||||
|
|
||||||
This document explains how to use the reusable polling system for NetCop AI agents.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The agent polling system provides a standardized way to handle asynchronous requests in agent templates, with proper cleanup, error handling, and user feedback.
|
|
||||||
|
|
||||||
## Key Features
|
|
||||||
|
|
||||||
- **Automatic cleanup**: Prevents memory leaks and duplicate polling
|
|
||||||
- **Error handling**: Handles network errors and timeouts gracefully
|
|
||||||
- **Duplicate prevention**: Ensures results are displayed only once
|
|
||||||
- **Progressive feedback**: Shows status steps for better UX
|
|
||||||
- **Reusable utilities**: Common functions for wallet updates, toasts, etc.
|
|
||||||
|
|
||||||
## Basic Usage
|
|
||||||
|
|
||||||
### 1. Include the Script
|
|
||||||
|
|
||||||
Add to your agent template's `extra_css` block:
|
|
||||||
|
|
||||||
```html
|
|
||||||
{% block extra_js %}
|
|
||||||
<script src="{% static 'js/agent-polling.js' %}"></script>
|
|
||||||
<script>
|
|
||||||
// Your agent-specific code here
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Set Up Polling
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// For agents that use async polling
|
|
||||||
function startPolling(requestId) {
|
|
||||||
const poller = window.pollingManager.createPoller('myAgent', {
|
|
||||||
requestId: requestId,
|
|
||||||
statusUrl: `/agents/my-agent/status/${requestId}/`,
|
|
||||||
maxPolls: 30,
|
|
||||||
pollInterval: 1000,
|
|
||||||
onComplete: (result) => {
|
|
||||||
AgentUtils.resetUI({
|
|
||||||
processingStatusId: 'processingStatus',
|
|
||||||
processButtonId: 'processButton',
|
|
||||||
resultsId: 'results',
|
|
||||||
buttonText: '🔄 Generate Again (5.00 AED)'
|
|
||||||
});
|
|
||||||
displayResults(result);
|
|
||||||
},
|
|
||||||
onError: (error) => {
|
|
||||||
AgentUtils.resetUI({
|
|
||||||
processingStatusId: 'processingStatus',
|
|
||||||
processButtonId: 'processButton',
|
|
||||||
buttonText: '🔄 Try Again (5.00 AED)'
|
|
||||||
});
|
|
||||||
AgentUtils.showToast('❌ Network error - please try again', 'error');
|
|
||||||
},
|
|
||||||
onTimeout: () => {
|
|
||||||
AgentUtils.resetUI({
|
|
||||||
processingStatusId: 'processingStatus',
|
|
||||||
processButtonId: 'processButton',
|
|
||||||
buttonText: '🔄 Try Again (5.00 AED)'
|
|
||||||
});
|
|
||||||
AgentUtils.showToast('❌ Processing timeout - please try again', 'error');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
poller.start();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Handle Form Submission
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
document.getElementById('myForm').addEventListener('submit', function(e) {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
// Validation
|
|
||||||
if (!isFormValid()) {
|
|
||||||
AgentUtils.showToast('Please fill in all required fields', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authentication check
|
|
||||||
if (!isAuthenticated) {
|
|
||||||
window.location.href = loginUrl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Balance check
|
|
||||||
if (userBalance < requiredAmount) {
|
|
||||||
AgentUtils.showToast(`Insufficient balance! You need ${requiredAmount} AED.`, 'error');
|
|
||||||
setTimeout(() => window.location.href = walletUrl, 2000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear any existing polling
|
|
||||||
window.pollingManager.stopAll();
|
|
||||||
|
|
||||||
// Show processing status
|
|
||||||
AgentUtils.showProcessing({
|
|
||||||
processingStatusId: 'processingStatus',
|
|
||||||
processButtonId: 'processButton',
|
|
||||||
resultsId: 'results',
|
|
||||||
processingText: '⏳ Processing...'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start status steps
|
|
||||||
const stepper = new StatusStepper([
|
|
||||||
'Analyzing request...',
|
|
||||||
'Processing data...',
|
|
||||||
'Generating results...',
|
|
||||||
'Finalizing output...'
|
|
||||||
], 'statusText');
|
|
||||||
stepper.start();
|
|
||||||
|
|
||||||
// Submit form
|
|
||||||
const formData = new FormData(this);
|
|
||||||
|
|
||||||
fetch(submitUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
headers: { 'X-Requested-With': 'XMLHttpRequest' }
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(result => {
|
|
||||||
stepper.stop();
|
|
||||||
|
|
||||||
if (result.success && result.request_id) {
|
|
||||||
// Start polling for async agents
|
|
||||||
startPolling(result.request_id);
|
|
||||||
} else {
|
|
||||||
// Handle immediate response
|
|
||||||
AgentUtils.resetUI({
|
|
||||||
processingStatusId: 'processingStatus',
|
|
||||||
processButtonId: 'processButton',
|
|
||||||
buttonText: '🔄 Try Again (5.00 AED)'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.error) {
|
|
||||||
AgentUtils.showToast(`❌ ${result.error}`, 'error');
|
|
||||||
} else {
|
|
||||||
displayResults(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
stepper.stop();
|
|
||||||
AgentUtils.resetUI({
|
|
||||||
processingStatusId: 'processingStatus',
|
|
||||||
processButtonId: 'processButton',
|
|
||||||
buttonText: '🔄 Try Again (5.00 AED)'
|
|
||||||
});
|
|
||||||
AgentUtils.showToast('❌ Network error - please try again', 'error');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Reset Function
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function resetForm() {
|
|
||||||
// Stop all polling
|
|
||||||
window.pollingManager.stopAll();
|
|
||||||
|
|
||||||
// Reset form
|
|
||||||
document.getElementById('myForm').reset();
|
|
||||||
|
|
||||||
// Reset UI
|
|
||||||
AgentUtils.resetUI({
|
|
||||||
processingStatusId: 'processingStatus',
|
|
||||||
processButtonId: 'processButton',
|
|
||||||
resultsId: 'results',
|
|
||||||
buttonText: '🚀 Generate (5.00 AED)'
|
|
||||||
});
|
|
||||||
|
|
||||||
AgentUtils.showToast('Form reset! Ready for another request.', 'success');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## API Reference
|
|
||||||
|
|
||||||
### AgentPoller Class
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const poller = new AgentPoller({
|
|
||||||
requestId: 'string', // Request ID to poll
|
|
||||||
statusUrl: 'string', // Status endpoint URL
|
|
||||||
maxPolls: 30, // Maximum poll attempts
|
|
||||||
pollInterval: 1000, // Poll interval in ms
|
|
||||||
onComplete: function(result) {}, // Success callback
|
|
||||||
onError: function(error) {}, // Error callback
|
|
||||||
onTimeout: function() {} // Timeout callback
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### PollingManager
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Create and start a poller
|
|
||||||
const poller = window.pollingManager.createPoller('pollerId', config);
|
|
||||||
poller.start();
|
|
||||||
|
|
||||||
// Stop specific poller
|
|
||||||
window.pollingManager.stopPoller('pollerId');
|
|
||||||
|
|
||||||
// Stop all pollers
|
|
||||||
window.pollingManager.stopAll();
|
|
||||||
```
|
|
||||||
|
|
||||||
### AgentUtils
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Update wallet balance
|
|
||||||
AgentUtils.updateWalletBalance(150.00);
|
|
||||||
|
|
||||||
// Reset UI elements
|
|
||||||
AgentUtils.resetUI({
|
|
||||||
processingStatusId: 'processingStatus',
|
|
||||||
processButtonId: 'processButton',
|
|
||||||
resultsId: 'results',
|
|
||||||
buttonText: 'Process Again'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show processing state
|
|
||||||
AgentUtils.showProcessing({
|
|
||||||
processingStatusId: 'processingStatus',
|
|
||||||
processButtonId: 'processButton',
|
|
||||||
resultsId: 'results',
|
|
||||||
processingText: '⏳ Working...'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show toast notification
|
|
||||||
AgentUtils.showToast('Success message', 'success');
|
|
||||||
AgentUtils.showToast('Error message', 'error');
|
|
||||||
```
|
|
||||||
|
|
||||||
### StatusStepper
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const stepper = new StatusStepper([
|
|
||||||
'Step 1...',
|
|
||||||
'Step 2...',
|
|
||||||
'Step 3...'
|
|
||||||
], 'statusTextElementId', 800); // 800ms interval
|
|
||||||
|
|
||||||
stepper.start();
|
|
||||||
stepper.stop();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Migration Guide
|
|
||||||
|
|
||||||
### Converting Existing Agents
|
|
||||||
|
|
||||||
1. **Include the script** in your template
|
|
||||||
2. **Replace polling logic** with `AgentPoller`
|
|
||||||
3. **Use `AgentUtils`** for common operations
|
|
||||||
4. **Add proper cleanup** in reset functions
|
|
||||||
5. **Use `StatusStepper`** for better UX
|
|
||||||
|
|
||||||
### Before (old way):
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Old polling code with potential issues
|
|
||||||
let pollInterval = setInterval(() => {
|
|
||||||
fetch(statusUrl)
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(result => {
|
|
||||||
if (result.status === 'completed') {
|
|
||||||
clearInterval(pollInterval);
|
|
||||||
displayResults(result);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
```
|
|
||||||
|
|
||||||
### After (new way):
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// New robust polling
|
|
||||||
const poller = window.pollingManager.createPoller('agent', {
|
|
||||||
requestId: requestId,
|
|
||||||
statusUrl: statusUrl,
|
|
||||||
onComplete: displayResults,
|
|
||||||
onError: handleError,
|
|
||||||
onTimeout: handleTimeout
|
|
||||||
});
|
|
||||||
poller.start();
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
1. **Always stop existing polling** before starting new requests
|
|
||||||
2. **Use unique poller IDs** for different agents/features
|
|
||||||
3. **Provide clear error messages** to users
|
|
||||||
4. **Set appropriate timeouts** based on expected processing time
|
|
||||||
5. **Clean up resources** in reset functions
|
|
||||||
6. **Use progressive status steps** for better UX
|
|
||||||
7. **Prevent duplicate submissions** with proper state management
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Common Issues
|
|
||||||
|
|
||||||
1. **Multiple polling instances**: Use `pollingManager.stopAll()` before starting new requests
|
|
||||||
2. **Memory leaks**: Always call `stop()` or use the manager's cleanup methods
|
|
||||||
3. **Duplicate results**: The system prevents this automatically
|
|
||||||
4. **Network errors**: Handled automatically with proper user feedback
|
|
||||||
|
|
||||||
### Debug Mode
|
|
||||||
|
|
||||||
Enable debug logging:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// In development
|
|
||||||
window.agentPollingDebug = true;
|
|
||||||
```
|
|
||||||
|
|
||||||
This will log polling activities to the console for debugging.
|
|
||||||
@ -1,574 +0,0 @@
|
|||||||
Perfect! Let's proceed with **inline code generation** for manual copying. Here's how we'll break it down:
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Step 1: Unified Base Template — `base_agent.html`
|
|
||||||
|
|
||||||
Create this file in your templates folder:
|
|
||||||
|
|
||||||
```html
|
|
||||||
{% extends "base.html" %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block title %}{{ agent_title }} - NetCop AI Hub{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_css %}
|
|
||||||
<link rel="stylesheet" href="{% static 'css/theme.css' %}">
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="agent-page">
|
|
||||||
<div class="agent-container">
|
|
||||||
|
|
||||||
{% include "components/agent_header.html" %}
|
|
||||||
|
|
||||||
{% block agent_main %}{% endblock %}
|
|
||||||
|
|
||||||
{% include "components/processing_status.html" %}
|
|
||||||
{% include "components/results_block.html" %}
|
|
||||||
{% include "components/quick_agents_panel.html" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧱 Step 2: Agent Header Component — `components/agent_header.html`
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div class="agent-header">
|
|
||||||
<div>
|
|
||||||
<h1 class="agent-title">{{ agent_title }}</h1>
|
|
||||||
<p class="agent-subtitle">{{ agent_subtitle }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="header-controls">
|
|
||||||
{% include "components/wallet_card.html" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💳 Step 3: Wallet Card Component — `components/wallet_card.html`
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div class="wallet-card widget-small" style="margin-bottom: 0;">
|
|
||||||
<div class="wallet-header">
|
|
||||||
<h3 class="wallet-title">Your Wallet</h3>
|
|
||||||
<div class="wallet-icon">💳</div>
|
|
||||||
</div>
|
|
||||||
<div class="balance-display">
|
|
||||||
<div class="balance-amount">
|
|
||||||
<span id="walletBalance">{{ user.wallet_balance|floatformat:2 }}</span> AED
|
|
||||||
</div>
|
|
||||||
<div class="balance-label">Available Balance</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧭 Step 4: Quick Agents Panel — `components/quick_agents_panel.html`
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div class="quick-agents-overlay" id="quickAgentsOverlay" onclick="closeQuickAgents()" aria-hidden="true"></div>
|
|
||||||
|
|
||||||
<div class="quick-agents-panel" id="quickAgentsPanel" role="dialog" aria-labelledby="quickAgentsTitle" aria-hidden="true">
|
|
||||||
<div class="quick-agents-header">
|
|
||||||
<h3 id="quickAgentsTitle">Quick Access to Other Agents</h3>
|
|
||||||
<button class="close-panel" onclick="toggleQuickAgents()" aria-label="Close quick agents panel">×</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="quick-agents-grid">
|
|
||||||
<a href="/agents/data-analyzer/" class="quick-agent-card">
|
|
||||||
<div class="agent-icon">📊</div>
|
|
||||||
<div class="agent-info">
|
|
||||||
<h4>Data Analyzer</h4>
|
|
||||||
<p>AI-powered data analysis</p>
|
|
||||||
<span class="agent-price">5.0 AED</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="/agents/weather-reporter/" class="quick-agent-card">
|
|
||||||
<div class="agent-icon">🌤️</div>
|
|
||||||
<div class="agent-info">
|
|
||||||
<h4>Weather Reporter</h4>
|
|
||||||
<p>Worldwide forecasts</p>
|
|
||||||
<span class="agent-price">2.0 AED</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="/agents/job-posting-generator/" class="quick-agent-card">
|
|
||||||
<div class="agent-icon">💼</div>
|
|
||||||
<div class="agent-info">
|
|
||||||
<h4>Job Posting Generator</h4>
|
|
||||||
<p>Create job posts</p>
|
|
||||||
<span class="agent-price">3.0 AED</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="quick-agents-footer">
|
|
||||||
<a href="{% url 'core:marketplace' %}" class="view-all-agents">View All Agents →</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Step 5: Results Block — `components/results_block.html`
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div class="agent-widget widget-wide results-container" id="resultsContainer" style="display: none;">
|
|
||||||
<div class="widget-header">
|
|
||||||
<h3 class="widget-title">
|
|
||||||
<span class="widget-icon">📊</span>
|
|
||||||
Analysis Results
|
|
||||||
</h3>
|
|
||||||
<span class="status-badge">Success</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="widget-content">
|
|
||||||
<div class="results-content" id="resultsContent"></div>
|
|
||||||
<div class="results-actions">
|
|
||||||
<button onclick="copyResults()" class="btn btn-primary">📋 Copy</button>
|
|
||||||
<button onclick="downloadResults()" class="btn btn-secondary">💾 Download</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⏳ Step 6: Processing Status — `components/processing_status.html`
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div id="processingStatus" class="agent-widget widget-wide processing-status">
|
|
||||||
<div class="widget-header">
|
|
||||||
<h3 class="widget-title">
|
|
||||||
<span class="widget-icon">⏳</span>
|
|
||||||
Processing
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<div class="widget-content" style="text-align: center;">
|
|
||||||
<div class="status-icon">⏳</div>
|
|
||||||
<div class="status-title">Working on your request...</div>
|
|
||||||
<div class="status-subtitle" id="statusText">Please wait...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ℹ️ Step 7: How It Works Widget — `components/how_it_works_widget.html`
|
|
||||||
|
|
||||||
```html
|
|
||||||
{% if steps == "data" %}
|
|
||||||
<ol class="info-list">
|
|
||||||
<li>Upload your data file</li>
|
|
||||||
<li>Choose analysis type</li>
|
|
||||||
<li>Get AI-powered insights</li>
|
|
||||||
<li>Copy or download results</li>
|
|
||||||
</ol>
|
|
||||||
{% elif steps == "weather" %}
|
|
||||||
<ol class="info-list">
|
|
||||||
<li>Enter any city name worldwide</li>
|
|
||||||
<li>Choose your preferred report type</li>
|
|
||||||
<li>Get real-time weather data</li>
|
|
||||||
<li>Copy or download detailed reports</li>
|
|
||||||
</ol>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<button class="quick-agent-toggle btn btn-secondary btn-full" onclick="toggleQuickAgents()"
|
|
||||||
title="Quick access to other agents">
|
|
||||||
<span class="toggle-icon">🚀</span>
|
|
||||||
<span class="toggle-text">Explore Other Agents</span>
|
|
||||||
</button>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📁 Optional Folder Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
templates/
|
|
||||||
│
|
|
||||||
├── base_agent.html
|
|
||||||
├── agents/
|
|
||||||
│ ├── data_analyzer.html
|
|
||||||
│ └── weather_reporter.html
|
|
||||||
├── components/
|
|
||||||
│ ├── agent_header.html
|
|
||||||
│ ├── wallet_card.html
|
|
||||||
│ ├── quick_agents_panel.html
|
|
||||||
│ ├── results_block.html
|
|
||||||
│ ├── processing_status.html
|
|
||||||
│ └── how_it_works_widget.html
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Would you like me to now generate the unified `theme.css`? Or do you want to manually merge the CSS too?
|
|
||||||
|
|
||||||
|
|
||||||
Here is your **unified `theme.css`** for both the Data Analyzer and Weather Reporter agents. This file consolidates colors, typography, layout, buttons, forms, radio buttons, result boxes, and responsive utilities.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ `static/css/theme.css`
|
|
||||||
|
|
||||||
```css
|
|
||||||
/* ========== NetCop Unified Theme ========== */
|
|
||||||
|
|
||||||
/* Fonts */
|
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
|
||||||
|
|
||||||
* {
|
|
||||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========== Root Variables ========== */
|
|
||||||
:root {
|
|
||||||
/* Colors */
|
|
||||||
--primary: #1a1a1a;
|
|
||||||
--primary-variant: #2d2d2d;
|
|
||||||
--background: #ffffff;
|
|
||||||
--surface: #ffffff;
|
|
||||||
--surface-variant: #f7fafc;
|
|
||||||
--outline: #e2e8f0;
|
|
||||||
--outline-variant: #cbd5e0;
|
|
||||||
|
|
||||||
--on-surface: #1a202c;
|
|
||||||
--on-surface-variant: #4a5568;
|
|
||||||
|
|
||||||
--success: #38a169;
|
|
||||||
--error: #e53e3e;
|
|
||||||
--warning: #d69e2e;
|
|
||||||
|
|
||||||
/* Spacing */
|
|
||||||
--spacing-xs: 4px;
|
|
||||||
--spacing-sm: 8px;
|
|
||||||
--spacing-md: 16px;
|
|
||||||
--spacing-lg: 24px;
|
|
||||||
--spacing-xl: 32px;
|
|
||||||
--spacing-2xl: 48px;
|
|
||||||
|
|
||||||
/* Radius */
|
|
||||||
--radius-sm: 8px;
|
|
||||||
--radius-md: 12px;
|
|
||||||
--radius-lg: 16px;
|
|
||||||
|
|
||||||
/* Shadows */
|
|
||||||
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
|
|
||||||
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
||||||
--shadow-lg: 0 10px 20px rgba(0, 0, 0, 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========== Layout ========== */
|
|
||||||
body {
|
|
||||||
background: var(--background);
|
|
||||||
color: var(--on-surface);
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Utility Classes */
|
|
||||||
.text-center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.flex {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.flex-col {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.flex-row {
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
.gap-md {
|
|
||||||
gap: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========== Agent Container ========== */
|
|
||||||
.agent-page {
|
|
||||||
min-height: 100vh;
|
|
||||||
padding: var(--spacing-lg) 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.agent-container {
|
|
||||||
max-width: 1600px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 var(--spacing-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========== Header ========== */
|
|
||||||
.agent-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
.agent-title {
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: -0.5px;
|
|
||||||
}
|
|
||||||
.agent-subtitle {
|
|
||||||
font-size: 16px;
|
|
||||||
color: var(--on-surface-variant);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========== Wallet Card ========== */
|
|
||||||
.wallet-card {
|
|
||||||
background: linear-gradient(135deg, #000000 0%, #333333 100%);
|
|
||||||
color: white;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
position: relative;
|
|
||||||
box-shadow: var(--shadow-md);
|
|
||||||
}
|
|
||||||
.wallet-card::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
border-radius: 50%;
|
|
||||||
transform: translate(40px, -40px);
|
|
||||||
}
|
|
||||||
.wallet-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: var(--spacing-sm);
|
|
||||||
}
|
|
||||||
.wallet-title {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
.wallet-icon {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
.balance-amount {
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: -0.5px;
|
|
||||||
}
|
|
||||||
.balance-label {
|
|
||||||
font-size: 12px;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========== Widget ========== */
|
|
||||||
.agent-widget {
|
|
||||||
background: var(--surface);
|
|
||||||
border: 1px solid var(--outline);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
padding: var(--spacing-xl);
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
}
|
|
||||||
.widget-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: var(--spacing-lg);
|
|
||||||
border-bottom: 1px solid var(--outline-variant);
|
|
||||||
padding-bottom: var(--spacing-md);
|
|
||||||
}
|
|
||||||
.widget-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
}
|
|
||||||
.widget-icon {
|
|
||||||
font-size: 20px;
|
|
||||||
padding: 6px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
background: var(--surface-variant);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========== Buttons ========== */
|
|
||||||
.btn {
|
|
||||||
padding: 12px 24px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
text-decoration: none;
|
|
||||||
cursor: pointer;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
min-height: 44px;
|
|
||||||
}
|
|
||||||
.btn-primary {
|
|
||||||
background: var(--primary);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.btn-primary:hover {
|
|
||||||
background: var(--primary-variant);
|
|
||||||
}
|
|
||||||
.btn-secondary {
|
|
||||||
background: var(--surface);
|
|
||||||
color: var(--on-surface);
|
|
||||||
border: 2px solid var(--outline);
|
|
||||||
}
|
|
||||||
.btn-secondary:hover {
|
|
||||||
background: var(--surface-variant);
|
|
||||||
}
|
|
||||||
.btn-full {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========== Form Elements ========== */
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: var(--spacing-lg);
|
|
||||||
}
|
|
||||||
.form-label {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: var(--spacing-sm);
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.form-input,
|
|
||||||
.form-textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px 16px;
|
|
||||||
border: 2px solid var(--outline-variant);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
background: var(--surface);
|
|
||||||
font-size: 14px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
.form-input:focus,
|
|
||||||
.form-textarea:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--primary);
|
|
||||||
box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.form-textarea {
|
|
||||||
resize: vertical;
|
|
||||||
min-height: 120px;
|
|
||||||
}
|
|
||||||
.form-help {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--on-surface-variant);
|
|
||||||
}
|
|
||||||
.form-error {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--error);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========== Radio Buttons ========== */
|
|
||||||
.radio-grid {
|
|
||||||
display: grid;
|
|
||||||
gap: var(--spacing-md);
|
|
||||||
}
|
|
||||||
.radio-option,
|
|
||||||
.radio-card {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: var(--spacing-md);
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
border: 2px solid var(--outline-variant);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
cursor: pointer;
|
|
||||||
background: var(--surface);
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
.radio-option:hover,
|
|
||||||
.radio-card:hover {
|
|
||||||
border-color: var(--primary);
|
|
||||||
background: var(--surface-variant);
|
|
||||||
}
|
|
||||||
.radio-option.selected,
|
|
||||||
.radio-card.selected {
|
|
||||||
border-color: var(--primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========== Results ========== */
|
|
||||||
.results-container {
|
|
||||||
background: var(--surface);
|
|
||||||
border: 1px solid var(--outline-variant);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
padding: var(--spacing-xl);
|
|
||||||
}
|
|
||||||
.results-content {
|
|
||||||
background: var(--surface-variant);
|
|
||||||
padding: var(--spacing-xl);
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
font-size: 15px;
|
|
||||||
line-height: 1.7;
|
|
||||||
}
|
|
||||||
.results-content h1,
|
|
||||||
.results-content h2,
|
|
||||||
.results-content h3 {
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--primary);
|
|
||||||
}
|
|
||||||
.results-content h1 {
|
|
||||||
font-size: 24px;
|
|
||||||
border-bottom: 3px solid var(--primary);
|
|
||||||
padding-bottom: var(--spacing-sm);
|
|
||||||
margin-bottom: var(--spacing-lg);
|
|
||||||
}
|
|
||||||
.results-content h2 {
|
|
||||||
font-size: 20px;
|
|
||||||
position: relative;
|
|
||||||
padding-left: var(--spacing-md);
|
|
||||||
}
|
|
||||||
.results-content h2::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 4px;
|
|
||||||
background: var(--primary);
|
|
||||||
}
|
|
||||||
.results-content h3 {
|
|
||||||
font-size: 18px;
|
|
||||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
|
||||||
border-left: 4px solid var(--primary);
|
|
||||||
padding: var(--spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========== Responsive ========== */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.agent-header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: var(--spacing-sm);
|
|
||||||
}
|
|
||||||
.agent-title {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
.wallet-card {
|
|
||||||
padding: var(--spacing-lg);
|
|
||||||
}
|
|
||||||
.balance-amount {
|
|
||||||
font-size: 28px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ What's Next?
|
|
||||||
|
|
||||||
* Place this file at: `static/css/theme.css`
|
|
||||||
* Link it in all agent templates using `{% static %}`
|
|
||||||
* Remove embedded `<style>` blocks from individual templates
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Would you like me to now refactor either `data_analyzer.html` or `weather_reporter.html` to use this new theme and component system?
|
|
||||||
|
|
||||||
@ -1,228 +0,0 @@
|
|||||||
# 📘 setup-guide.md — NetCop AI Hub Agent Frontend Refactor
|
|
||||||
|
|
||||||
This guide will help you modularize your AI agent pages using the **Data Analyzer layout as the base template** and build a reusable, theme-driven UI system.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ 1. Define Folder Structure
|
|
||||||
|
|
||||||
Create the following Django template structure:
|
|
||||||
|
|
||||||
```
|
|
||||||
templates/
|
|
||||||
├── base_agent.html
|
|
||||||
├── agents/
|
|
||||||
│ ├── data_analyzer.html
|
|
||||||
│ └── weather_reporter.html
|
|
||||||
├── components/
|
|
||||||
│ ├── agent_header.html
|
|
||||||
│ ├── wallet_card.html
|
|
||||||
│ ├── how_it_works_widget.html
|
|
||||||
│ ├── quick_agents_panel.html
|
|
||||||
│ ├── results_block.html
|
|
||||||
│ ├── processing_status.html
|
|
||||||
│ └── ... (other shared components)
|
|
||||||
static/
|
|
||||||
└── css/
|
|
||||||
└── theme.css
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎨 2. Create `theme.css`
|
|
||||||
|
|
||||||
Save this as `static/css/theme.css` and link it in `base_agent.html`.
|
|
||||||
|
|
||||||
> This file includes unified tokens: spacing, colors, radius, shadows, typography, responsive styles.
|
|
||||||
|
|
||||||
(Refer to the generated `theme.css` in the conversation.)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📐 3. Create `base_agent.html`
|
|
||||||
|
|
||||||
```django
|
|
||||||
{% extends "base.html" %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block title %}{{ agent_title }} - NetCop AI Hub{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_css %}
|
|
||||||
<link rel="stylesheet" href="{% static 'css/theme.css' %}">
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="agent-page">
|
|
||||||
<div class="agent-container">
|
|
||||||
{% include "components/agent_header.html" %}
|
|
||||||
{% block agent_main %}{% endblock %}
|
|
||||||
{% include "components/processing_status.html" %}
|
|
||||||
{% include "components/results_block.html" %}
|
|
||||||
{% include "components/quick_agents_panel.html" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧩 4. Extract Components
|
|
||||||
|
|
||||||
### `agent_header.html`
|
|
||||||
|
|
||||||
```django
|
|
||||||
<div class="agent-header">
|
|
||||||
<div>
|
|
||||||
<h1 class="agent-title">{{ agent_title }}</h1>
|
|
||||||
<p class="agent-subtitle">{{ agent_subtitle }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="header-controls">
|
|
||||||
{% include "components/wallet_card.html" %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### `wallet_card.html`
|
|
||||||
|
|
||||||
```django
|
|
||||||
<div class="wallet-card widget-small" style="margin-bottom: 0;">
|
|
||||||
<div class="wallet-header">
|
|
||||||
<h3 class="wallet-title">Your Wallet</h3>
|
|
||||||
<div class="wallet-icon">💳</div>
|
|
||||||
</div>
|
|
||||||
<div class="balance-display">
|
|
||||||
<div class="balance-amount">
|
|
||||||
<span id="walletBalance">{{ user.wallet_balance|floatformat:2 }}</span> AED
|
|
||||||
</div>
|
|
||||||
<div class="balance-label">Available Balance</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### `how_it_works_widget.html`
|
|
||||||
|
|
||||||
```django
|
|
||||||
<div class="agent-widget widget-small" style="min-width: min(280px, 100%); max-width: min(280px, 100%); margin-left: auto;">
|
|
||||||
<div class="widget-header">
|
|
||||||
<h3 class="widget-title">
|
|
||||||
<span class="widget-icon">ℹ️</span>
|
|
||||||
How It Works
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<div class="widget-content">
|
|
||||||
{% if steps == "data" %}
|
|
||||||
<ol class="info-list">
|
|
||||||
<li>Upload your data file</li>
|
|
||||||
<li>Choose analysis type</li>
|
|
||||||
<li>Get AI-powered insights</li>
|
|
||||||
<li>Copy or download results</li>
|
|
||||||
</ol>
|
|
||||||
{% elif steps == "weather" %}
|
|
||||||
<ol class="info-list">
|
|
||||||
<li>Enter any city name worldwide</li>
|
|
||||||
<li>Choose your preferred report type</li>
|
|
||||||
<li>Get real-time weather data</li>
|
|
||||||
<li>Copy or download detailed reports</li>
|
|
||||||
</ol>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<button class="quick-agent-toggle btn btn-secondary btn-full" onclick="toggleQuickAgents()">
|
|
||||||
<span class="toggle-icon">🚀</span>
|
|
||||||
<span class="toggle-text">Explore Other Agents</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### `quick_agents_panel.html`
|
|
||||||
|
|
||||||
```django
|
|
||||||
<div class="quick-agents-overlay" id="quickAgentsOverlay" onclick="closeQuickAgents()" aria-hidden="true"></div>
|
|
||||||
<div class="quick-agents-panel" id="quickAgentsPanel" role="dialog" aria-labelledby="quickAgentsTitle" aria-hidden="true">
|
|
||||||
<div class="quick-agents-header">
|
|
||||||
<h3 id="quickAgentsTitle">Quick Access to Other Agents</h3>
|
|
||||||
<button class="close-panel" onclick="toggleQuickAgents()" aria-label="Close quick agents panel">×</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="quick-agents-grid">
|
|
||||||
<a href="/agents/data-analyzer/" class="quick-agent-card">
|
|
||||||
<div class="agent-icon">📊</div>
|
|
||||||
<div class="agent-info">
|
|
||||||
<h4>Data Analyzer</h4>
|
|
||||||
<p>AI-powered data analysis</p>
|
|
||||||
<span class="agent-price">5.0 AED</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<!-- Add more agents here -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="quick-agents-footer">
|
|
||||||
<a href="{% url 'core:marketplace' %}" class="view-all-agents">View All Agents →</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### `results_block.html`
|
|
||||||
|
|
||||||
```django
|
|
||||||
<div class="agent-widget widget-wide results-container" id="resultsContainer" style="display: none;">
|
|
||||||
<div class="widget-header">
|
|
||||||
<h3 class="widget-title">
|
|
||||||
<span class="widget-icon">📊</span>
|
|
||||||
Analysis Results
|
|
||||||
</h3>
|
|
||||||
<span class="status-badge">Success</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="widget-content">
|
|
||||||
<div class="results-content" id="resultsContent"></div>
|
|
||||||
<div class="results-actions">
|
|
||||||
<button onclick="copyResults()" class="btn btn-primary">📋 Copy</button>
|
|
||||||
<button onclick="downloadResults()" class="btn btn-secondary">💾 Download</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### `processing_status.html`
|
|
||||||
|
|
||||||
```django
|
|
||||||
<div id="processingStatus" class="agent-widget widget-wide processing-status">
|
|
||||||
<div class="widget-header">
|
|
||||||
<h3 class="widget-title">
|
|
||||||
<span class="widget-icon">⏳</span>
|
|
||||||
Processing Status
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<div class="widget-content" style="text-align: center;">
|
|
||||||
<div class="status-icon">⏳</div>
|
|
||||||
<div class="status-title">Analyzing your data...</div>
|
|
||||||
<div class="status-subtitle" id="statusText">Processing file...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔁 5. Convert `data_analyzer.html` to Use Base Template
|
|
||||||
|
|
||||||
```django
|
|
||||||
{% extends "base_agent.html" %}
|
|
||||||
{% block agent_main %}
|
|
||||||
<div class="agent-grid">
|
|
||||||
<div class="agent-widget widget-large">
|
|
||||||
<!-- Include Data Analyzer form here -->
|
|
||||||
<form>...</form>
|
|
||||||
</div>
|
|
||||||
{% include "components/how_it_works_widget.html" with steps="data" %}
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧠 6. All Other Agents Will:
|
|
||||||
|
|
||||||
- Extend `base_agent.html`
|
|
||||||
- Use `{% block agent_main %}` for agent-specific content
|
|
||||||
- Include `how_it_works_widget.html` and proper context
|
|
||||||
@ -116,6 +116,9 @@ const AgentUtils = FiveWhysUtils;
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="agent-page theme-professional">
|
<div class="agent-page theme-professional">
|
||||||
<div class="agent-container">
|
<div class="agent-container">
|
||||||
|
<!-- Quick Agent Access Panel -->
|
||||||
|
{% include "components/quick_agents_panel.html" %}
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div>
|
<div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@ -218,7 +221,7 @@ const AgentUtils = FiveWhysUtils;
|
|||||||
<div class="balance-label">Available Balance</div>
|
<div class="balance-label">Available Balance</div>
|
||||||
|
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<a href="{% url 'core:wallet' %}" class="btn btn-primary" style="text-decoration: none; margin-top: 16px;">
|
<a href="{% url 'wallet:wallet' %}" class="btn btn-primary" style="text-decoration: none; margin-top: 16px;">
|
||||||
💰 Top Up Wallet
|
💰 Top Up Wallet
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|||||||
@ -965,7 +965,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
document.body.setAttribute('data-user-authenticated', '{{ user.is_authenticated|yesno:"true,false" }}');
|
document.body.setAttribute('data-user-authenticated', '{{ user.is_authenticated|yesno:"true,false" }}');
|
||||||
document.body.setAttribute('data-login-url', '{% url "authentication:login" %}');
|
document.body.setAttribute('data-login-url', '{% url "authentication:login" %}');
|
||||||
document.body.setAttribute('data-wallet-balance', '{{ user.wallet_balance|default:0 }}');
|
document.body.setAttribute('data-wallet-balance', '{{ user.wallet_balance|default:0 }}');
|
||||||
document.body.setAttribute('data-wallet-url', '{% url "core:wallet" %}');
|
document.body.setAttribute('data-wallet-url', '{% url "wallet:wallet" %}');
|
||||||
|
|
||||||
// Initialize form enhancements
|
// Initialize form enhancements
|
||||||
initializeFormEnhancements();
|
initializeFormEnhancements();
|
||||||
@ -1241,7 +1241,7 @@ document.addEventListener('keydown', function(e) {
|
|||||||
<div style="background: #fef2f2; color: #dc2626; padding: var(--spacing-md); border-radius: var(--radius-md); margin-bottom: var(--spacing-md); font-size: 14px; font-weight: 500; text-align: center;">
|
<div style="background: #fef2f2; color: #dc2626; padding: var(--spacing-md); border-radius: var(--radius-md); margin-bottom: var(--spacing-md); font-size: 14px; font-weight: 500; text-align: center;">
|
||||||
Insufficient balance! You need 4.00 AED.
|
Insufficient balance! You need 4.00 AED.
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'core:wallet' %}" class="btn btn-primary btn-full" style="text-decoration: none;">
|
<a href="{% url 'wallet:wallet' %}" class="btn btn-primary btn-full" style="text-decoration: none;">
|
||||||
💰 Top Up Wallet
|
💰 Top Up Wallet
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -1287,43 +1287,12 @@ document.addEventListener('keydown', function(e) {
|
|||||||
|
|
||||||
<!-- Processing Status -->
|
<!-- Processing Status -->
|
||||||
<div class="agent-grid" style="margin-top: var(--spacing-lg);">
|
<div class="agent-grid" style="margin-top: var(--spacing-lg);">
|
||||||
<div id="processingStatus" class="agent-widget widget-wide processing-status">
|
{% include "components/processing_status.html" with status_title="Creating Job Posting..." status_text="Crafting professional job description..." %}
|
||||||
<div class="widget-header">
|
|
||||||
<h3 class="widget-title">
|
|
||||||
<span class="widget-icon">⏳</span>
|
|
||||||
Processing Status
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<div class="widget-content" style="text-align: center;">
|
|
||||||
<div class="status-icon">💼</div>
|
|
||||||
<div class="status-text">Creating Job Posting...</div>
|
|
||||||
<div class="status-detail" id="statusText">Crafting professional job description...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Results -->
|
<!-- Results -->
|
||||||
<div class="agent-grid" style="margin-top: var(--spacing-lg);">
|
<div class="agent-grid" style="margin-top: var(--spacing-lg);">
|
||||||
<div id="jobResults" class="agent-widget widget-wide results-card">
|
{% include "components/results_container.html" with results_title="Generated Job Posting" %}
|
||||||
<div class="widget-header">
|
|
||||||
<h3 class="widget-title">
|
|
||||||
<span class="widget-icon">✅</span>
|
|
||||||
Generated Job Posting
|
|
||||||
</h3>
|
|
||||||
<div style="background: var(--primary); color: white; padding: 6px 12px; border-radius: 6px; font-size: 14px; font-weight: 600;">✅ Complete</div>
|
|
||||||
</div>
|
|
||||||
<div class="widget-content">
|
|
||||||
<div class="results-content" id="jobContent">
|
|
||||||
<!-- Job posting content will be displayed here -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="action-buttons">
|
|
||||||
<button onclick="copyJobPosting()" class="btn btn-primary">📋 Copy Job Posting</button>
|
|
||||||
<button onclick="downloadJobPosting()" class="btn btn-secondary">💾 Download Posting</button>
|
|
||||||
<button onclick="resetForm()" class="btn" style="background: var(--primary); color: white;">🔄 Create Another</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -1701,7 +1670,7 @@ document.addEventListener('keydown', function(e) {
|
|||||||
if (currentBalance < 4.00) {
|
if (currentBalance < 4.00) {
|
||||||
AgentUtils.showToast('Insufficient balance! You need 4.00 AED.', 'error');
|
AgentUtils.showToast('Insufficient balance! You need 4.00 AED.', 'error');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.href = "{% url 'core:wallet' %}";
|
window.location.href = "{% url 'wallet:wallet' %}";
|
||||||
}, 2000);
|
}, 2000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,8 @@ from django.conf.urls.static import static
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('auth/', include('authentication.urls')),
|
path('auth/', include('authentication.urls')),
|
||||||
|
path('wallet/', include('wallet.urls')),
|
||||||
|
path('', include('agent_base.urls')),
|
||||||
path('agents/weather-reporter/', include('weather_reporter.urls')),
|
path('agents/weather-reporter/', include('weather_reporter.urls')),
|
||||||
path('agents/data-analyzer/', include('data_analyzer.urls')),
|
path('agents/data-analyzer/', include('data_analyzer.urls')),
|
||||||
path('agents/job-posting-generator/', include('job_posting_generator.urls')),
|
path('agents/job-posting-generator/', include('job_posting_generator.urls')),
|
||||||
|
|||||||
@ -1,4 +0,0 @@
|
|||||||
Django==5.2.4
|
|
||||||
djangorestframework==3.16.0
|
|
||||||
python-decouple==3.8
|
|
||||||
stripe==12.3.0
|
|
||||||
@ -1455,7 +1455,7 @@ if (document.readyState === 'loading') {
|
|||||||
<div style="background: #fef2f2; color: #dc2626; padding: var(--spacing-md); border-radius: var(--radius-md); margin-bottom: var(--spacing-md); font-size: 14px; font-weight: 500; text-align: center;">
|
<div style="background: #fef2f2; color: #dc2626; padding: var(--spacing-md); border-radius: var(--radius-md); margin-bottom: var(--spacing-md); font-size: 14px; font-weight: 500; text-align: center;">
|
||||||
Insufficient balance! You need 7.00 AED.
|
Insufficient balance! You need 7.00 AED.
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'core:wallet' %}" class="btn btn-primary btn-full" style="text-decoration: none;">
|
<a href="{% url 'wallet:wallet' %}" class="btn btn-primary btn-full" style="text-decoration: none;">
|
||||||
💰 Top Up Wallet
|
💰 Top Up Wallet
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -1501,43 +1501,10 @@ if (document.readyState === 'loading') {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Processing Status -->
|
<!-- Processing Status -->
|
||||||
<div id="processingStatus" class="processing-status" aria-live="polite" aria-label="Processing request">
|
{% include "components/processing_status.html" with status_title="Creating Social Ads..." status_text="Generating engaging ad content..." %}
|
||||||
<div class="status-icon">📢</div>
|
|
||||||
<div style="font-weight: 600; color: var(--primary);">Creating Social Ads...</div>
|
|
||||||
<div style="font-size: 14px; color: var(--on-surface-variant); margin-top: 8px;" id="statusText">
|
|
||||||
Generating engaging ad content...
|
|
||||||
</div>
|
|
||||||
<span class="sr-only">Processing your request...</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Results -->
|
<!-- Results -->
|
||||||
<div id="adResults" class="results-card" role="region" aria-labelledby="resultsTitle">
|
{% include "components/results_container.html" with results_title="Generated Social Ads" %}
|
||||||
<div class="results-header">
|
|
||||||
<div style="font-size: 24px;">✅</div>
|
|
||||||
<h3 id="resultsTitle" style="font-size: 20px; font-weight: 600; color: var(--on-surface); margin: 0;">
|
|
||||||
Generated Social Ads
|
|
||||||
</h3>
|
|
||||||
<div style="background: var(--success); color: white; padding: 6px 12px; border-radius: 6px; font-size: 14px; font-weight: 600; margin-left: auto;">
|
|
||||||
✅ Complete
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="results-content" id="adContent">
|
|
||||||
<!-- Ad content will be displayed here -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="action-buttons">
|
|
||||||
<button onclick="copySocialAds()" class="btn btn-primary" type="button">
|
|
||||||
📋 Copy Ads
|
|
||||||
</button>
|
|
||||||
<button onclick="downloadSocialAds()" class="btn btn-secondary" type="button">
|
|
||||||
💾 Download Ads
|
|
||||||
</button>
|
|
||||||
<button onclick="resetForm()" class="btn btn-secondary" type="button">
|
|
||||||
🔄 Create Another
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
@ -1913,7 +1880,7 @@ if (document.readyState === 'loading') {
|
|||||||
// Check wallet balance dynamically
|
// Check wallet balance dynamically
|
||||||
if (!AgentUtils.validateWalletBalance()) {
|
if (!AgentUtils.validateWalletBalance()) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.href = "{% url 'core:wallet' %}";
|
window.location.href = "{% url 'wallet:wallet' %}";
|
||||||
}, 2000);
|
}, 2000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
/* Base Styles for NetCop Hub */
|
/* Base Styles for NetCop Hub */
|
||||||
|
|
||||||
|
/* Utility Classes */
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Unified Color System */
|
/* Unified Color System */
|
||||||
:root {
|
:root {
|
||||||
/* Primary Colors */
|
/* Primary Colors */
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
/* ================================================================
|
|
||||||
Legacy Header CSS - DEPRECATED
|
|
||||||
================================================================
|
|
||||||
|
|
||||||
This file is being phased out in favor of header-component.css
|
|
||||||
for better maintainability and consistency.
|
|
||||||
|
|
||||||
Only keeping essential styles that haven't been migrated yet.
|
|
||||||
================================================================ */
|
|
||||||
|
|
||||||
/* This file is now primarily empty - all header styling has been
|
|
||||||
moved to header-component.css for better architecture */
|
|
||||||
@ -36,12 +36,12 @@
|
|||||||
|
|
||||||
<!-- Category Filters -->
|
<!-- Category Filters -->
|
||||||
<div class="category-filters">
|
<div class="category-filters">
|
||||||
<a href="{% url 'core:marketplace' %}" class="category-button {% if not selected_category %}active{% endif %}" data-category="all">
|
<a href="{% url 'agent_base:marketplace' %}" class="category-button {% if not selected_category %}active{% endif %}" data-category="all">
|
||||||
<span class="category-emoji">🤖</span> All Assistants
|
<span class="category-emoji">🤖</span> All Assistants
|
||||||
</a>
|
</a>
|
||||||
{% if categories %}
|
{% if categories %}
|
||||||
{% for category_value, category_display in categories %}
|
{% for category_value, category_display in categories %}
|
||||||
<a href="{% url 'core:marketplace' %}?category={{ category_value }}"
|
<a href="{% url 'agent_base:marketplace' %}?category={{ category_value }}"
|
||||||
class="category-button {% if selected_category == category_value %}active{% endif %}"
|
class="category-button {% if selected_category == category_value %}active{% endif %}"
|
||||||
data-category="{{ category_value }}">
|
data-category="{{ category_value }}">
|
||||||
<span class="category-emoji">
|
<span class="category-emoji">
|
||||||
@ -91,9 +91,9 @@
|
|||||||
<span class="price-text">{{ agent.price }} AED</span>
|
<span class="price-text">{{ agent.price }} AED</span>
|
||||||
</div>
|
</div>
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<a href="{% url 'core:agent_detail' agent.slug %}" class="use-button">Use Now</a>
|
<a href="{% url 'agent_base:agent_detail' agent.slug %}" class="use-button">Use Now</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'authentication:login' %}?next={% url 'core:agent_detail' agent.slug %}" class="use-button">Login to Use</a>
|
<a href="{% url 'authentication:login' %}?next={% url 'agent_base:agent_detail' agent.slug %}" class="use-button">Login to Use</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -9,8 +9,8 @@
|
|||||||
<div class="wallet-status {{ wallet_status.status }}">
|
<div class="wallet-status {{ wallet_status.status }}">
|
||||||
<strong>{{ wallet_status.message }}</strong>
|
<strong>{{ wallet_status.message }}</strong>
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'core:wallet' %}" class="btn">Manage Wallet</a>
|
<a href="{% url 'wallet:wallet' %}" class="btn">Manage Wallet</a>
|
||||||
<a href="{% url 'core:wallet_topup' %}" class="btn">Top Up</a>
|
<a href="{% url 'wallet:wallet_topup' %}" class="btn">Top Up</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
|
|||||||
@ -30,7 +30,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<nav class="header-nav" id="header-nav">
|
<nav class="header-nav" id="header-nav">
|
||||||
<a href="{% url 'core:homepage' %}" class="nav-link {% if request.resolver_match.url_name == 'homepage' %}active{% endif %}">Home</a>
|
<a href="{% url 'core:homepage' %}" class="nav-link {% if request.resolver_match.url_name == 'homepage' %}active{% endif %}">Home</a>
|
||||||
<a href="{% url 'core:marketplace' %}" class="nav-link {% if request.resolver_match.url_name == 'marketplace' %}active{% endif %}">AI Marketplace</a>
|
<a href="{% url 'agent_base:marketplace' %}" class="nav-link {% if request.resolver_match.url_name == 'marketplace' %}active{% endif %}">AI Marketplace</a>
|
||||||
<a href="{% url 'core:pricing' %}" class="nav-link {% if request.resolver_match.url_name == 'pricing' %}active{% endif %}">Pricing</a>
|
<a href="{% url 'core:pricing' %}" class="nav-link {% if request.resolver_match.url_name == 'pricing' %}active{% endif %}">Pricing</a>
|
||||||
</nav>
|
</nav>
|
||||||
<button class="mobile-nav-toggle" onclick="toggleMobileNav()" aria-label="Toggle navigation">
|
<button class="mobile-nav-toggle" onclick="toggleMobileNav()" aria-label="Toggle navigation">
|
||||||
@ -40,9 +40,9 @@
|
|||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<p class="user-welcome">Welcome, {{ user.username }}!</p>
|
<p class="user-welcome">Welcome, {{ user.username }}!</p>
|
||||||
<a href="{% url 'core:wallet' %}" class="balance" data-wallet-balance>💰 {{ user.wallet_balance|floatformat:2 }} AED</a>
|
<a href="{% url 'wallet:wallet' %}" class="balance" data-wallet-balance>💰 {{ user.wallet_balance|floatformat:2 }} AED</a>
|
||||||
<div class="auth-links">
|
<div class="auth-links">
|
||||||
<a href="{% url 'core:wallet' %}">Wallet</a>
|
<a href="{% url 'wallet:wallet' %}">Wallet</a>
|
||||||
<a href="{% url 'authentication:logout' %}">Logout</a>
|
<a href="{% url 'authentication:logout' %}">Logout</a>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -105,7 +105,7 @@
|
|||||||
<a href="#company-profile" class="footer-nav-link">About Us</a>
|
<a href="#company-profile" class="footer-nav-link">About Us</a>
|
||||||
<a href="#founder" class="footer-nav-link">Leadership</a>
|
<a href="#founder" class="footer-nav-link">Leadership</a>
|
||||||
<a href="#contact" class="footer-nav-link">Contact Us</a>
|
<a href="#contact" class="footer-nav-link">Contact Us</a>
|
||||||
<a href="{% url 'core:marketplace' %}" class="footer-nav-link">AI Marketplace</a>
|
<a href="{% url 'agent_base:marketplace' %}" class="footer-nav-link">AI Marketplace</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -54,6 +54,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="quick-agents-footer">
|
<div class="quick-agents-footer">
|
||||||
<a href="{% url 'core:marketplace' %}" class="view-all-agents">View All Agents →</a>
|
<a href="{% url 'agent_base:marketplace' %}" class="view-all-agents">View All Agents →</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -1,4 +1,4 @@
|
|||||||
<div class="agent-widget widget-wide results-container" id="resultsContainer" style="display: none;">
|
<div class="agent-widget widget-wide results-container hidden" id="resultsContainer">
|
||||||
<div class="widget-header">
|
<div class="widget-header">
|
||||||
<h3 class="widget-title">
|
<h3 class="widget-title">
|
||||||
<span class="widget-icon">📊</span>
|
<span class="widget-icon">📊</span>
|
||||||
|
|||||||
@ -41,17 +41,17 @@
|
|||||||
<!-- CTA Buttons -->
|
<!-- CTA Buttons -->
|
||||||
<div class="hero-buttons">
|
<div class="hero-buttons">
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<a href="{% url 'core:marketplace' %}" class="btn-primary">
|
<a href="{% url 'agent_base:marketplace' %}" class="btn-primary">
|
||||||
Explore AI Hub
|
Explore AI Hub
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'core:wallet' %}" class="btn-secondary">
|
<a href="{% url 'wallet:wallet' %}" class="btn-secondary">
|
||||||
Manage Wallet
|
Manage Wallet
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'authentication:register' %}" class="btn-primary">
|
<a href="{% url 'authentication:register' %}" class="btn-primary">
|
||||||
Get Protected Now
|
Get Protected Now
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'core:marketplace' %}" class="btn-secondary">
|
<a href="{% url 'agent_base:marketplace' %}" class="btn-secondary">
|
||||||
Explore AI Hub
|
Explore AI Hub
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -94,7 +94,7 @@
|
|||||||
aria-label="Create account to get started">
|
aria-label="Create account to get started">
|
||||||
🚀 Create Account
|
🚀 Create Account
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'core:marketplace' %}"
|
<a href="{% url 'agent_base:marketplace' %}"
|
||||||
class="cta-btn secondary"
|
class="cta-btn secondary"
|
||||||
aria-label="Browse available AI agents">
|
aria-label="Browse available AI agents">
|
||||||
🤖 View Marketplace
|
🤖 View Marketplace
|
||||||
@ -139,11 +139,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="quick-actions">
|
<div class="quick-actions">
|
||||||
<a href="{% url 'core:marketplace' %}" class="action-btn">
|
<a href="{% url 'agent_base:marketplace' %}" class="action-btn">
|
||||||
🤖 Browse AI Agents
|
🤖 Browse AI Agents
|
||||||
</a>
|
</a>
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<a href="{% url 'core:wallet' %}" class="action-btn">
|
<a href="{% url 'wallet:wallet' %}" class="action-btn">
|
||||||
💳 View Wallet
|
💳 View Wallet
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@ -594,7 +594,7 @@
|
|||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<h2 id="balance-heading" class="balance-label">💰 Current Balance</h2>
|
<h2 id="balance-heading" class="balance-label">💰 Current Balance</h2>
|
||||||
<div class="balance-amount" aria-live="polite" data-wallet-balance>{{ current_balance|floatformat:2 }} AED</div>
|
<div class="balance-amount" aria-live="polite" data-wallet-balance>{{ current_balance|floatformat:2 }} AED</div>
|
||||||
<a href="{% url 'core:wallet_topup' %}"
|
<a href="{% url 'wallet:wallet_topup' %}"
|
||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
style="background: white; color: black; border: 1px solid rgba(255,255,255,0.2);"
|
style="background: white; color: black; border: 1px solid rgba(255,255,255,0.2);"
|
||||||
aria-label="Top up wallet balance">
|
aria-label="Top up wallet balance">
|
||||||
@ -700,7 +700,7 @@
|
|||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<div class="empty-icon">📭</div>
|
<div class="empty-icon">📭</div>
|
||||||
<p class="empty-text">No transactions yet. Start using AI agents to see your transaction history!</p>
|
<p class="empty-text">No transactions yet. Start using AI agents to see your transaction history!</p>
|
||||||
<a href="{% url 'core:marketplace' %}" class="btn btn-primary">🤖 Browse Agents</a>
|
<a href="{% url 'agent_base:marketplace' %}" class="btn btn-primary">🤖 Browse Agents</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -716,10 +716,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="quick-actions">
|
<div class="quick-actions">
|
||||||
<a href="{% url 'core:wallet_topup' %}" class="action-btn">
|
<a href="{% url 'wallet:wallet_topup' %}" class="action-btn">
|
||||||
💳 Top Up Wallet
|
💳 Top Up Wallet
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'core:marketplace' %}" class="action-btn">
|
<a href="{% url 'agent_base:marketplace' %}" class="action-btn">
|
||||||
🤖 Browse Agents
|
🤖 Browse Agents
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'core:homepage' %}" class="action-btn">
|
<a href="{% url 'core:homepage' %}" class="action-btn">
|
||||||
@ -436,7 +436,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="topup-page theme-professional">
|
<div class="topup-page theme-professional">
|
||||||
<a href="{% url 'core:wallet' %}" class="back-to-wallet">
|
<a href="{% url 'wallet:wallet' %}" class="back-to-wallet">
|
||||||
← Back to Wallet
|
← Back to Wallet
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@ -565,10 +565,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<div class="quick-actions">
|
<div class="quick-actions">
|
||||||
<a href="{% url 'core:wallet' %}" class="action-btn">
|
<a href="{% url 'wallet:wallet' %}" class="action-btn">
|
||||||
📊 View Transactions
|
📊 View Transactions
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'core:marketplace' %}" class="action-btn">
|
<a href="{% url 'agent_base:marketplace' %}" class="action-btn">
|
||||||
🤖 Browse Agents
|
🤖 Browse Agents
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'core:homepage' %}" class="action-btn">
|
<a href="{% url 'core:homepage' %}" class="action-btn">
|
||||||
@ -4,7 +4,7 @@ import sys
|
|||||||
import django
|
import django
|
||||||
|
|
||||||
# Add the project root to Python path
|
# Add the project root to Python path
|
||||||
sys.path.insert(0, '/home/amit/projects/netcop_django')
|
sys.path.insert(0, '/home/amit/Desktop/quantum_ai')
|
||||||
|
|
||||||
# Set Django settings
|
# Set Django settings
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'netcop_hub.settings')
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'netcop_hub.settings')
|
||||||
@ -70,7 +70,7 @@ def check_url_structure():
|
|||||||
homepage_url = reverse('core:homepage')
|
homepage_url = reverse('core:homepage')
|
||||||
print(f"✅ Homepage URL: {homepage_url}")
|
print(f"✅ Homepage URL: {homepage_url}")
|
||||||
|
|
||||||
marketplace_url = reverse('core:marketplace')
|
marketplace_url = reverse('agent_base:marketplace')
|
||||||
print(f"✅ Marketplace URL: {marketplace_url}")
|
print(f"✅ Marketplace URL: {marketplace_url}")
|
||||||
|
|
||||||
# Test weather reporter URL
|
# Test weather reporter URL
|
||||||
|
|||||||
13
wallet/urls.py
Normal file
13
wallet/urls.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = 'wallet'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.wallet_view, name='wallet'),
|
||||||
|
path('topup/', views.wallet_topup_view, name='wallet_topup'),
|
||||||
|
path('top-up/success/', views.wallet_topup_success_view, name='wallet_topup_success'),
|
||||||
|
path('top-up/cancel/', views.wallet_topup_cancel_view, name='wallet_topup_cancel'),
|
||||||
|
path('stripe/debug/', views.stripe_debug_view, name='stripe_debug'),
|
||||||
|
path('stripe/webhook/', views.stripe_webhook_view, name='stripe_webhook'),
|
||||||
|
]
|
||||||
245
wallet/views.py
245
wallet/views.py
@ -1,3 +1,244 @@
|
|||||||
from django.shortcuts import render
|
from django.shortcuts import render, redirect
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.contrib import messages
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from .stripe_handler import StripePaymentHandler
|
||||||
|
import datetime
|
||||||
|
import stripe
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
# Create your views here.
|
# Global webhook logs for debugging (in production, use proper logging)
|
||||||
|
webhook_logs = []
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def wallet_view(request):
|
||||||
|
"""Wallet management page"""
|
||||||
|
transactions = request.user.wallet_transactions.all()[:50]
|
||||||
|
|
||||||
|
# Calculate statistics
|
||||||
|
total_spent = sum(abs(t.amount) for t in transactions if t.type == 'agent_usage')
|
||||||
|
total_topped_up = sum(t.amount for t in transactions if t.type == 'top_up')
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'transactions': transactions,
|
||||||
|
'total_spent': total_spent,
|
||||||
|
'total_topped_up': total_topped_up,
|
||||||
|
'current_balance': request.user.wallet_balance,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, 'wallet/wallet.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def wallet_topup_view(request):
|
||||||
|
"""Wallet top-up page"""
|
||||||
|
if request.method == 'POST':
|
||||||
|
amount = request.POST.get('amount')
|
||||||
|
|
||||||
|
try:
|
||||||
|
amount = float(amount)
|
||||||
|
if amount not in [10, 50, 100, 500]:
|
||||||
|
messages.error(request, 'Invalid amount selected')
|
||||||
|
return redirect('wallet:wallet_topup')
|
||||||
|
|
||||||
|
# Create Stripe checkout session
|
||||||
|
stripe_handler = StripePaymentHandler()
|
||||||
|
session_data = stripe_handler.create_checkout_session(request.user, amount, request)
|
||||||
|
|
||||||
|
return redirect(session_data['payment_url'])
|
||||||
|
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
messages.error(request, 'Invalid amount')
|
||||||
|
return redirect('wallet:wallet_topup')
|
||||||
|
|
||||||
|
return render(request, 'wallet/wallet_topup.html')
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def wallet_topup_success_view(request):
|
||||||
|
"""Payment success page with automatic payment verification (NO WEBHOOKS NEEDED)"""
|
||||||
|
session_id = request.GET.get('session_id')
|
||||||
|
|
||||||
|
if not session_id:
|
||||||
|
messages.error(request, 'No payment session found. Please contact support if you completed a payment.')
|
||||||
|
return redirect('wallet:wallet')
|
||||||
|
|
||||||
|
# Verify payment directly with Stripe API (bypasses webhook issues)
|
||||||
|
try:
|
||||||
|
stripe_handler = StripePaymentHandler()
|
||||||
|
|
||||||
|
print(f"💳 [SUCCESS PAGE] Verifying payment for session: {session_id}")
|
||||||
|
result = stripe_handler.verify_payment(session_id)
|
||||||
|
|
||||||
|
if result['success']:
|
||||||
|
if result['processed']:
|
||||||
|
messages.success(request, f'Payment successful! {result["amount"]} AED has been added to your wallet.')
|
||||||
|
print(f"✅ [SUCCESS PAGE] Payment verified and wallet updated for user {request.user.id}")
|
||||||
|
else:
|
||||||
|
messages.info(request, 'Payment already processed. Your wallet balance is up to date.')
|
||||||
|
print(f"ℹ️ [SUCCESS PAGE] Payment already processed for session {session_id}")
|
||||||
|
else:
|
||||||
|
messages.warning(request, f'Payment verification failed: {result.get("error", "Unknown error")}. Please contact support.')
|
||||||
|
print(f"❌ [SUCCESS PAGE] Payment verification failed: {result}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ [SUCCESS PAGE] Error verifying payment: {e}")
|
||||||
|
messages.error(request, 'Unable to verify payment. Please contact support if you completed a payment.')
|
||||||
|
|
||||||
|
return redirect('wallet:wallet')
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def wallet_topup_cancel_view(request):
|
||||||
|
"""Payment cancel page"""
|
||||||
|
messages.info(request, 'Payment was cancelled. No charges were made.')
|
||||||
|
return redirect('wallet:wallet_topup')
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def stripe_debug_view(request):
|
||||||
|
"""Debug endpoint to show Stripe API configuration and test connectivity"""
|
||||||
|
debug_info = {
|
||||||
|
'timestamp': datetime.datetime.now().isoformat(),
|
||||||
|
'user_id': request.user.id,
|
||||||
|
'user_email': request.user.email,
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Test Stripe API connectivity
|
||||||
|
print(f"🔍 [STRIPE DEBUG] Testing Stripe API connectivity...")
|
||||||
|
|
||||||
|
# Get API key info (masked)
|
||||||
|
api_key = settings.STRIPE_SECRET_KEY
|
||||||
|
debug_info['stripe_api_key_last4'] = api_key[-4:] if api_key else 'Not set'
|
||||||
|
debug_info['stripe_api_key_prefix'] = api_key[:7] if api_key else 'Not set'
|
||||||
|
debug_info['stripe_api_version'] = stripe.api_version
|
||||||
|
|
||||||
|
# Test account connectivity
|
||||||
|
try:
|
||||||
|
account = stripe.Account.retrieve()
|
||||||
|
debug_info['stripe_account'] = {
|
||||||
|
'id': account.id,
|
||||||
|
'email': account.email,
|
||||||
|
'display_name': account.display_name,
|
||||||
|
'country': account.country,
|
||||||
|
'default_currency': account.default_currency,
|
||||||
|
'business_profile': account.business_profile,
|
||||||
|
'charges_enabled': account.charges_enabled,
|
||||||
|
'payouts_enabled': account.payouts_enabled,
|
||||||
|
}
|
||||||
|
print(f"✅ [STRIPE DEBUG] Account connected: {account.id}")
|
||||||
|
except Exception as account_error:
|
||||||
|
debug_info['stripe_account_error'] = str(account_error)
|
||||||
|
print(f"❌ [STRIPE DEBUG] Account error: {account_error}")
|
||||||
|
|
||||||
|
# Test recent checkout sessions
|
||||||
|
try:
|
||||||
|
sessions = stripe.checkout.Session.list(limit=5)
|
||||||
|
debug_info['recent_sessions'] = []
|
||||||
|
for session in sessions.data:
|
||||||
|
debug_info['recent_sessions'].append({
|
||||||
|
'id': session.id,
|
||||||
|
'status': session.status,
|
||||||
|
'payment_status': session.payment_status,
|
||||||
|
'amount_total': session.amount_total,
|
||||||
|
'currency': session.currency,
|
||||||
|
'customer_email': session.customer_email,
|
||||||
|
'client_reference_id': session.client_reference_id,
|
||||||
|
'created': session.created,
|
||||||
|
'metadata': session.metadata,
|
||||||
|
})
|
||||||
|
print(f"✅ [STRIPE DEBUG] Retrieved {len(sessions.data)} recent sessions")
|
||||||
|
except Exception as sessions_error:
|
||||||
|
debug_info['sessions_error'] = str(sessions_error)
|
||||||
|
print(f"❌ [STRIPE DEBUG] Sessions error: {sessions_error}")
|
||||||
|
|
||||||
|
# Test recent payments
|
||||||
|
try:
|
||||||
|
charges = stripe.Charge.list(limit=5)
|
||||||
|
debug_info['recent_charges'] = []
|
||||||
|
for charge in charges.data:
|
||||||
|
debug_info['recent_charges'].append({
|
||||||
|
'id': charge.id,
|
||||||
|
'amount': charge.amount,
|
||||||
|
'currency': charge.currency,
|
||||||
|
'status': charge.status,
|
||||||
|
'paid': charge.paid,
|
||||||
|
'customer': charge.customer,
|
||||||
|
'description': charge.description,
|
||||||
|
'created': charge.created,
|
||||||
|
'metadata': charge.metadata,
|
||||||
|
})
|
||||||
|
print(f"✅ [STRIPE DEBUG] Retrieved {len(charges.data)} recent charges")
|
||||||
|
except Exception as charges_error:
|
||||||
|
debug_info['charges_error'] = str(charges_error)
|
||||||
|
print(f"❌ [STRIPE DEBUG] Charges error: {charges_error}")
|
||||||
|
|
||||||
|
debug_info['status'] = 'success'
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
debug_info['error'] = str(e)
|
||||||
|
debug_info['status'] = 'error'
|
||||||
|
print(f"❌ [STRIPE DEBUG] General error: {e}")
|
||||||
|
|
||||||
|
return JsonResponse(debug_info, indent=2)
|
||||||
|
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
def stripe_webhook_view(request):
|
||||||
|
"""Handle Stripe webhook events with comprehensive logging"""
|
||||||
|
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
|
||||||
|
|
||||||
|
# Log everything for debugging
|
||||||
|
print(f"🎯 [{timestamp}] Stripe webhook received!")
|
||||||
|
print(f"🎯 Method: {request.method}")
|
||||||
|
print(f"🎯 Content-Type: {request.content_type}")
|
||||||
|
print(f"🎯 Remote IP: {request.META.get('REMOTE_ADDR', 'unknown')}")
|
||||||
|
print(f"🎯 User Agent: {request.META.get('HTTP_USER_AGENT', 'unknown')}")
|
||||||
|
print(f"🎯 Full headers: {dict(request.META)}")
|
||||||
|
|
||||||
|
# Store in webhook logs for the test page
|
||||||
|
webhook_log_entry = {
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'method': request.method,
|
||||||
|
'headers': dict(request.META),
|
||||||
|
'body': request.body.decode('utf-8') if request.body else '',
|
||||||
|
'content_type': request.content_type,
|
||||||
|
'source': 'stripe_webhook',
|
||||||
|
'ip_address': request.META.get('REMOTE_ADDR', 'unknown'),
|
||||||
|
'user_agent': request.META.get('HTTP_USER_AGENT', 'unknown')
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add to webhook logs
|
||||||
|
webhook_logs.append(webhook_log_entry)
|
||||||
|
if len(webhook_logs) > 50:
|
||||||
|
webhook_logs.pop(0)
|
||||||
|
|
||||||
|
if request.method != 'POST':
|
||||||
|
print(f"❌ Invalid method: {request.method}")
|
||||||
|
return JsonResponse({'status': 'error', 'message': f'Method {request.method} not allowed'}, status=405)
|
||||||
|
|
||||||
|
payload = request.body
|
||||||
|
sig_header = request.META.get('HTTP_STRIPE_SIGNATURE')
|
||||||
|
|
||||||
|
print(f"📦 Payload length: {len(payload)} bytes")
|
||||||
|
print(f"📦 Payload preview: {payload[:200]}...")
|
||||||
|
print(f"🔐 Signature header: {sig_header is not None}")
|
||||||
|
print(f"🔐 Full signature header: {sig_header}")
|
||||||
|
|
||||||
|
# Always return success first to see if Stripe is reaching us
|
||||||
|
if not sig_header:
|
||||||
|
print(f"⚠️ No Stripe signature - might be a test request")
|
||||||
|
return JsonResponse({'status': 'received', 'message': 'No signature verification'})
|
||||||
|
|
||||||
|
stripe_handler = StripePaymentHandler()
|
||||||
|
result = stripe_handler.handle_webhook(payload, sig_header)
|
||||||
|
|
||||||
|
print(f"✅ Webhook result: {result}")
|
||||||
|
|
||||||
|
if result['success']:
|
||||||
|
return JsonResponse({'status': 'success'})
|
||||||
|
else:
|
||||||
|
return JsonResponse({'status': 'error', 'message': result['error']}, status=400)
|
||||||
@ -278,7 +278,7 @@ function handleFormSubmission(e) {
|
|||||||
if (walletBalance < 2.00) {
|
if (walletBalance < 2.00) {
|
||||||
WeatherUtils.showToast('Insufficient wallet balance', 'error');
|
WeatherUtils.showToast('Insufficient wallet balance', 'error');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.href = "{% url 'core:wallet' %}";
|
window.location.href = "{% url 'wallet:wallet' %}";
|
||||||
}, 2000);
|
}, 2000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -323,75 +323,10 @@ function handleFormSubmission(e) {
|
|||||||
|
|
||||||
<div class="agent-container">
|
<div class="agent-container">
|
||||||
<!-- Agent Header -->
|
<!-- Agent Header -->
|
||||||
<div class="agent-header">
|
{% include "components/agent_header.html" with agent_title="Weather Reporter" agent_subtitle="Get real-time weather data from any location worldwide" %}
|
||||||
<div>
|
|
||||||
<h1 class="agent-title">Weather Reporter</h1>
|
|
||||||
<p class="agent-subtitle">Get real-time weather data from any location worldwide</p>
|
|
||||||
</div>
|
|
||||||
<div class="header-controls">
|
|
||||||
<div class="wallet-card widget-small" style="margin-bottom: 0;">
|
|
||||||
<div class="wallet-header">
|
|
||||||
<h3 class="wallet-title">Your Wallet</h3>
|
|
||||||
<div class="wallet-icon">💳</div>
|
|
||||||
</div>
|
|
||||||
<div class="balance-display">
|
|
||||||
<div class="balance-amount">
|
|
||||||
<span id="walletBalance">{{ user.wallet_balance|floatformat:2 }}</span> AED
|
|
||||||
</div>
|
|
||||||
<div class="balance-label">Available Balance</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Quick Agent Access Panel -->
|
<!-- Quick Agent Access Panel -->
|
||||||
<div class="quick-agents-overlay" id="quickAgentsOverlay" onclick="closeQuickAgents()" aria-hidden="true"></div>
|
{% include "components/quick_agents_panel.html" %}
|
||||||
<div class="quick-agents-panel" id="quickAgentsPanel" role="dialog" aria-labelledby="quickAgentsTitle" aria-hidden="true">
|
|
||||||
<div class="quick-agents-header">
|
|
||||||
<h3 id="quickAgentsTitle">Quick Access to Other Agents</h3>
|
|
||||||
<button class="close-panel" onclick="toggleQuickAgents()" aria-label="Close quick agents panel">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="quick-agents-grid">
|
|
||||||
<a href="/agents/data-analyzer/" class="quick-agent-card">
|
|
||||||
<div class="agent-icon">📊</div>
|
|
||||||
<div class="agent-info">
|
|
||||||
<h4>Data Analyzer</h4>
|
|
||||||
<p>AI-powered data analysis</p>
|
|
||||||
<span class="agent-price">5.0 AED</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="/agents/job-posting-generator/" class="quick-agent-card">
|
|
||||||
<div class="agent-icon">💼</div>
|
|
||||||
<div class="agent-info">
|
|
||||||
<h4>Job Posting Generator</h4>
|
|
||||||
<p>Create professional job posts</p>
|
|
||||||
<span class="agent-price">4.0 AED</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="/agents/social-ads-generator/" class="quick-agent-card">
|
|
||||||
<div class="agent-icon">📢</div>
|
|
||||||
<div class="agent-info">
|
|
||||||
<h4>Social Ads Generator</h4>
|
|
||||||
<p>Create social media campaigns</p>
|
|
||||||
<span class="agent-price">7.0 AED</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a href="/agents/five-whys-analyzer/" class="quick-agent-card">
|
|
||||||
<div class="agent-icon">🤔</div>
|
|
||||||
<div class="agent-info">
|
|
||||||
<h4>Five Whys Analyzer</h4>
|
|
||||||
<p>Problem analysis method</p>
|
|
||||||
<span class="agent-price">3.0 AED</span>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="quick-agents-footer">
|
|
||||||
<a href="{% url 'core:marketplace' %}" class="view-all-agents">View All Agents →</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Agent Grid -->
|
<!-- Agent Grid -->
|
||||||
<div class="agent-grid">
|
<div class="agent-grid">
|
||||||
@ -448,7 +383,7 @@ function handleFormSubmission(e) {
|
|||||||
<div style="background: #fef2f2; color: #dc2626; padding: var(--spacing-md); border-radius: var(--radius-md); margin-bottom: var(--spacing-md); font-size: 14px; font-weight: 500; text-align: center;">
|
<div style="background: #fef2f2; color: #dc2626; padding: var(--spacing-md); border-radius: var(--radius-md); margin-bottom: var(--spacing-md); font-size: 14px; font-weight: 500; text-align: center;">
|
||||||
Insufficient balance! You need 2.00 AED.
|
Insufficient balance! You need 2.00 AED.
|
||||||
</div>
|
</div>
|
||||||
<a href="{% url 'core:wallet' %}" class="btn btn-primary btn-full" style="text-decoration: none;">
|
<a href="{% url 'wallet:wallet' %}" class="btn btn-primary btn-full" style="text-decoration: none;">
|
||||||
💰 Top Up Wallet
|
💰 Top Up Wallet
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@ -495,41 +430,10 @@ function handleFormSubmission(e) {
|
|||||||
<!-- Main Content Grid -->
|
<!-- Main Content Grid -->
|
||||||
<div class="agent-grid">
|
<div class="agent-grid">
|
||||||
<!-- Processing Status -->
|
<!-- Processing Status -->
|
||||||
<div id="processingStatus" class="agent-widget widget-wide processing-status">
|
{% include "components/processing_status.html" with status_title="Getting Weather Data..." status_text="Fetching real-time weather information..." %}
|
||||||
<div class="widget-header">
|
|
||||||
<h3 class="widget-title">
|
|
||||||
<span class="widget-icon">⏳</span>
|
|
||||||
Processing Status
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
<div class="widget-content" style="text-align: center;">
|
|
||||||
<div class="status-icon">⏳</div>
|
|
||||||
<div class="status-text">Getting Weather Data...</div>
|
|
||||||
<div class="status-detail" id="statusText">Fetching real-time weather information...</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Results Widget -->
|
<!-- Results Widget -->
|
||||||
<div id="resultsContainer" class="agent-widget widget-wide results-container">
|
{% include "components/results_container.html" with results_title="Weather Report" %}
|
||||||
<div class="widget-header">
|
|
||||||
<h3 class="widget-title">
|
|
||||||
<span class="widget-icon">📊</span>
|
|
||||||
Weather Report
|
|
||||||
</h3>
|
|
||||||
<span class="status-badge" style="background: var(--success-color); color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px;">Success</span>
|
|
||||||
</div>
|
|
||||||
<div class="widget-content">
|
|
||||||
<div class="results-content" id="resultsContent">
|
|
||||||
<!-- Results will be populated here by JavaScript -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="action-buttons" style="margin-top: var(--spacing-lg); padding-top: var(--spacing-lg); border-top: 1px solid var(--outline-variant);">
|
|
||||||
<button onclick="copyResults()" class="btn btn-primary">📋 Copy Report</button>
|
|
||||||
<button onclick="downloadResults()" class="btn btn-secondary">💾 Download</button>
|
|
||||||
<button onclick="resetForm()" class="btn btn-secondary">🔄 New Request</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user