🧹 Major codebase cleanup - remove obsolete files and reduce project bloat

Removed obsolete files across multiple categories:
- AGENTS_APP_RECREATION_GUIDE.md (640 lines) - obsolete recreation guide
- 4 HTML prototype files (agent_template_prototype.html, social_ads*.html, workflow_template.html)
- Log files and database dumps (netcop.log, server.log, local_database_dump.json, docs_update_summary.txt)
- 6 obsolete test files for old agent systems and webhooks
- 4 unused static JS files (data-analyzer.js, workflows*.js, text-summarizer.js)
- 2 unused CSS files (digital-branding.css, workflows.css)

This cleanup reduces codebase size significantly while maintaining all 8 active agents and core functionality.
File-based agent system remains fully operational with streamlined, relevant files only.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude 2025-08-14 09:42:56 +05:30
parent e4f3e7993a
commit 2f17475445
17 changed files with 0 additions and 18017 deletions

View File

@ -1,639 +0,0 @@
# Django Agents App Recreation Guide
This guide provides complete instructions for recreating the agents app in another Django project.
## Overview
Create a Django app called `agents` with the following functionality:
- Agent marketplace with categories
- Agent execution system with n8n webhook integration
- User balance checking and fee deduction
- Complete REST API with pagination
- Admin interface for management
## Installation Steps
### 1. Create the App
```bash
python manage.py startapp agents
```
### 2. Install Dependencies
```bash
pip install requests djangorestframework
```
### 3. Add to INSTALLED_APPS
In your `settings.py`:
```python
INSTALLED_APPS = [
# ... other apps
'rest_framework',
'agents',
]
```
### 4. Add to URLs
In your main `urls.py`:
```python
from django.urls import path, include
urlpatterns = [
# ... other URLs
path('api/agents/', include('agents.urls')),
]
```
## File Structure
```
agents/
├── __init__.py
├── admin.py
├── apps.py
├── models.py
├── serializers.py
├── views.py
├── urls.py
├── migrations/
│ └── __init__.py
└── management/
└── commands/
└── create_sample_agents.py
```
## Code Files
### agents/models.py
```python
from django.db import models
import uuid
class AgentCategory(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
description = models.TextField(blank=True)
icon = models.CharField(max_length=50, blank=True, help_text="Icon class or emoji")
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['name']
def __str__(self):
return self.name
class Agent(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
short_description = models.CharField(max_length=300)
description = models.TextField()
category = models.ForeignKey(AgentCategory, on_delete=models.CASCADE, related_name='agents')
price = models.DecimalField(max_digits=10, decimal_places=2)
form_schema = models.JSONField(help_text="JSON schema for agent input form")
webhook_url = models.URLField(help_text="n8n webhook URL for execution")
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ['name']
def __str__(self):
return self.name
class AgentExecution(models.Model):
STATUS_CHOICES = [
('pending', 'Pending'),
('running', 'Running'),
('completed', 'Completed'),
('failed', 'Failed'),
]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
agent = models.ForeignKey(Agent, on_delete=models.CASCADE, related_name='executions')
user = models.ForeignKey('users.User', on_delete=models.CASCADE) # Adjust to your user model
input_data = models.JSONField()
output_data = models.JSONField(null=True, blank=True)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
fee_charged = models.DecimalField(max_digits=10, decimal_places=2)
webhook_response = models.JSONField(null=True, blank=True)
error_message = models.TextField(blank=True)
execution_time = models.DurationField(null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
completed_at = models.DateTimeField(null=True, blank=True)
class Meta:
ordering = ['-created_at']
def __str__(self):
return f"{self.agent.name} - {self.user.email} - {self.status}"
```
### agents/admin.py
```python
from django.contrib import admin
from .models import AgentCategory, Agent, AgentExecution
@admin.register(AgentCategory)
class AgentCategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'is_active', 'created_at']
list_filter = ['is_active', 'created_at']
search_fields = ['name', 'description']
prepopulated_fields = {'slug': ('name',)}
@admin.register(Agent)
class AgentAdmin(admin.ModelAdmin):
list_display = ['name', 'category', 'price', 'is_active', 'created_at']
list_filter = ['category', 'is_active', 'created_at']
search_fields = ['name', 'description', 'short_description']
prepopulated_fields = {'slug': ('name',)}
readonly_fields = ['created_at', 'updated_at']
@admin.register(AgentExecution)
class AgentExecutionAdmin(admin.ModelAdmin):
list_display = ['agent', 'user', 'status', 'fee_charged', 'created_at']
list_filter = ['status', 'created_at', 'agent__category']
search_fields = ['agent__name', 'user__email']
readonly_fields = ['created_at', 'completed_at']
```
### agents/serializers.py
```python
from rest_framework import serializers
from .models import Agent, AgentCategory, AgentExecution
class AgentCategorySerializer(serializers.ModelSerializer):
class Meta:
model = AgentCategory
fields = ['id', 'name', 'slug', 'description', 'icon']
class AgentSerializer(serializers.ModelSerializer):
category = AgentCategorySerializer(read_only=True)
class Meta:
model = Agent
fields = [
'id', 'name', 'slug', 'short_description', 'description',
'category', 'price', 'form_schema', 'created_at'
]
class AgentExecutionSerializer(serializers.ModelSerializer):
agent = AgentSerializer(read_only=True)
class Meta:
model = AgentExecution
fields = [
'id', 'agent', 'input_data', 'output_data', 'status',
'fee_charged', 'error_message', 'execution_time',
'created_at', 'completed_at'
]
```
### agents/views.py
```python
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
from django.shortcuts import get_object_or_404
from django.utils import timezone
from .models import Agent, AgentExecution
from .serializers import AgentSerializer, AgentExecutionSerializer
import requests
import json
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def agent_list(request):
"""List all active agents with optional category filtering"""
agents = Agent.objects.filter(is_active=True)
category = request.GET.get('category')
if category:
agents = agents.filter(category__slug=category)
search = request.GET.get('search')
if search:
agents = agents.filter(name__icontains=search)
paginator = PageNumberPagination()
paginator.page_size = 20
result_page = paginator.paginate_queryset(agents, request)
serializer = AgentSerializer(result_page, many=True)
return paginator.get_paginated_response(serializer.data)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def agent_detail(request, slug):
"""Get detailed agent information"""
agent = get_object_or_404(Agent, slug=slug, is_active=True)
serializer = AgentSerializer(agent)
return Response(serializer.data)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def execute_agent(request):
"""Execute an agent with provided input data"""
agent_slug = request.data.get('agent_slug')
input_data = request.data.get('input_data', {})
if not agent_slug:
return Response({'error': 'agent_slug is required'}, status=status.HTTP_400_BAD_REQUEST)
agent = get_object_or_404(Agent, slug=agent_slug, is_active=True)
# Check if user has sufficient balance (adjust based on your wallet system)
if hasattr(request.user, 'wallet_balance') and request.user.wallet_balance < agent.price:
return Response({'error': 'Insufficient wallet balance'}, status=status.HTTP_400_BAD_REQUEST)
# Create execution record
execution = AgentExecution.objects.create(
agent=agent,
user=request.user,
input_data=input_data,
fee_charged=agent.price,
status='pending'
)
try:
# Deduct fee from user wallet (adjust based on your wallet system)
if hasattr(request.user, 'deduct_balance'):
request.user.deduct_balance(agent.price)
# Call n8n webhook
execution.status = 'running'
execution.save()
webhook_payload = {
'execution_id': str(execution.id),
'agent_slug': agent.slug,
'user_id': str(request.user.id),
'input_data': input_data
}
response = requests.post(
agent.webhook_url,
json=webhook_payload,
timeout=30
)
execution.webhook_response = response.json() if response.headers.get('content-type', '').startswith('application/json') else {'raw': response.text}
if response.status_code == 200:
execution.status = 'completed'
execution.output_data = execution.webhook_response
else:
execution.status = 'failed'
execution.error_message = f"Webhook returned {response.status_code}"
execution.completed_at = timezone.now()
execution.save()
serializer = AgentExecutionSerializer(execution)
return Response(serializer.data, status=status.HTTP_201_CREATED)
except requests.RequestException as e:
execution.status = 'failed'
execution.error_message = str(e)
execution.completed_at = timezone.now()
execution.save()
return Response({
'error': 'Failed to execute agent',
'execution_id': str(execution.id)
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def execution_list(request):
"""List user's agent executions"""
executions = AgentExecution.objects.filter(user=request.user)
paginator = PageNumberPagination()
paginator.page_size = 20
result_page = paginator.paginate_queryset(executions, request)
serializer = AgentExecutionSerializer(result_page, many=True)
return paginator.get_paginated_response(serializer.data)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def execution_detail(request, execution_id):
"""Get detailed execution information"""
execution = get_object_or_404(AgentExecution, id=execution_id, user=request.user)
serializer = AgentExecutionSerializer(execution)
return Response(serializer.data)
```
### agents/urls.py
```python
from django.urls import path
from . import views
urlpatterns = [
path('', views.agent_list, name='agent_list'),
path('<slug:slug>/', views.agent_detail, name='agent_detail'),
path('execute/', views.execute_agent, name='execute_agent'),
path('executions/', views.execution_list, name='execution_list'),
path('executions/<uuid:execution_id>/', views.execution_detail, name='execution_detail'),
]
```
### agents/management/commands/create_sample_agents.py
First create the directories:
```bash
mkdir -p agents/management/commands
touch agents/management/__init__.py
touch agents/management/commands/__init__.py
```
Then create the file:
```python
from django.core.management.base import BaseCommand
from agents.models import AgentCategory, Agent
class Command(BaseCommand):
help = 'Create sample agents for testing'
def handle(self, *args, **options):
# Create categories
ai_category, _ = AgentCategory.objects.get_or_create(
slug='ai-tools',
defaults={
'name': 'AI Tools',
'description': 'AI-powered automation tools',
'icon': '🤖'
}
)
data_category, _ = AgentCategory.objects.get_or_create(
slug='data-analysis',
defaults={
'name': 'Data Analysis',
'description': 'Data processing and analysis tools',
'icon': '📊'
}
)
web_category, _ = AgentCategory.objects.get_or_create(
slug='web-scraping',
defaults={
'name': 'Web Scraping',
'description': 'Web data extraction tools',
'icon': '🕷️'
}
)
# Create sample agents
Agent.objects.get_or_create(
slug='pdf-analyzer',
defaults={
'name': 'PDF Content Analyzer',
'short_description': 'Extract and analyze content from PDF documents',
'description': 'This agent processes PDF files and extracts meaningful insights including summaries, keywords, and sentiment analysis. Perfect for document processing workflows.',
'category': ai_category,
'price': 5.00,
'form_schema': {
'fields': [
{
'name': 'pdf_url',
'type': 'url',
'label': 'PDF URL',
'placeholder': 'https://example.com/document.pdf',
'required': True
},
{
'name': 'analysis_type',
'type': 'select',
'label': 'Analysis Type',
'options': [
{'value': 'summary', 'label': 'Summary'},
{'value': 'keywords', 'label': 'Keywords'},
{'value': 'sentiment', 'label': 'Sentiment Analysis'}
],
'required': True
}
]
},
'webhook_url': 'https://your-n8n-instance.com/webhook/pdf-analyzer'
}
)
Agent.objects.get_or_create(
slug='website-scraper',
defaults={
'name': 'Website Data Scraper',
'short_description': 'Extract structured data from any website',
'description': 'Advanced web scraping agent that can extract specific data from websites using CSS selectors or XPath. Handles JavaScript-rendered content and returns clean, structured data.',
'category': web_category,
'price': 3.00,
'form_schema': {
'fields': [
{
'name': 'website_url',
'type': 'url',
'label': 'Website URL',
'placeholder': 'https://example.com',
'required': True
},
{
'name': 'selectors',
'type': 'textarea',
'label': 'CSS Selectors (one per line)',
'placeholder': 'h1.title\n.price\n.description',
'required': True
},
{
'name': 'wait_for_js',
'type': 'checkbox',
'label': 'Wait for JavaScript to load',
'required': False
}
]
},
'webhook_url': 'https://your-n8n-instance.com/webhook/website-scraper'
}
)
Agent.objects.get_or_create(
slug='data-analyzer',
defaults={
'name': 'CSV Data Analyzer',
'short_description': 'Analyze and visualize CSV data with insights',
'description': 'Upload CSV files and get comprehensive data analysis including statistics, trends, and visualizations. Perfect for business intelligence and data exploration.',
'category': data_category,
'price': 4.50,
'form_schema': {
'fields': [
{
'name': 'csv_url',
'type': 'url',
'label': 'CSV File URL',
'placeholder': 'https://example.com/data.csv',
'required': True
},
{
'name': 'analysis_columns',
'type': 'text',
'label': 'Columns to Analyze (comma-separated)',
'placeholder': 'sales,revenue,date',
'required': False
},
{
'name': 'chart_type',
'type': 'select',
'label': 'Chart Type',
'options': [
{'value': 'line', 'label': 'Line Chart'},
{'value': 'bar', 'label': 'Bar Chart'},
{'value': 'pie', 'label': 'Pie Chart'},
{'value': 'scatter', 'label': 'Scatter Plot'}
],
'required': False
}
]
},
'webhook_url': 'https://your-n8n-instance.com/webhook/data-analyzer'
}
)
self.stdout.write(self.style.SUCCESS('Sample agents created successfully'))
self.stdout.write(f'Created categories: {AgentCategory.objects.count()}')
self.stdout.write(f'Created agents: {Agent.objects.count()}')
```
### agents/apps.py
```python
from django.apps import AppConfig
class AgentsConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'agents'
verbose_name = 'Agents'
```
## Setup Instructions
### 1. Run Migrations
```bash
python manage.py makemigrations agents
python manage.py migrate
```
### 2. Create Sample Data
```bash
python manage.py create_sample_agents
```
### 3. Create Superuser (if needed)
```bash
python manage.py createsuperuser
```
### 4. Test the API
Start the server and test these endpoints:
- `GET /api/agents/` - List all agents
- `GET /api/agents/pdf-analyzer/` - Agent details
- `POST /api/agents/execute/` - Execute an agent
- `GET /api/agents/executions/` - List executions
## API Usage Examples
### List Agents
```bash
curl -H "Authorization: Token YOUR_TOKEN" http://localhost:8000/api/agents/
```
### Execute Agent
```bash
curl -X POST \
-H "Authorization: Token YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"agent_slug": "pdf-analyzer",
"input_data": {
"pdf_url": "https://example.com/document.pdf",
"analysis_type": "summary"
}
}' \
http://localhost:8000/api/agents/execute/
```
## Customization Notes
### User Model Integration
Update the `AgentExecution` model to reference your custom user model:
```python
# If your user model is in a different app
user = models.ForeignKey('accounts.CustomUser', on_delete=models.CASCADE)
```
### Wallet Integration
The code assumes your user model has these methods:
- `wallet_balance` property
- `deduct_balance(amount)` method
Adjust the wallet checking logic in `execute_agent` view as needed.
### n8n Webhook Format
The webhook payload sent to n8n includes:
- `execution_id`: UUID of the execution
- `agent_slug`: Identifier for the agent
- `user_id`: User who triggered the execution
- `input_data`: Form data submitted by user
## Features Included
**Agent Categories** - Organize agents by type
**Agent Management** - Full CRUD via Django admin
**Execution System** - Track agent runs with status
**Webhook Integration** - Connect to n8n workflows
**User Balance Checking** - Wallet integration ready
**REST API** - Complete API endpoints
**Pagination** - Built-in pagination for lists
**Error Handling** - Comprehensive error management
**Sample Data** - Management command for test data
**Form Schema** - Dynamic form generation support
**Admin Interface** - Django admin integration
**UUID Primary Keys** - Better security and uniqueness
## Production Considerations
1. **Environment Variables**: Store webhook URLs and API keys in environment variables
2. **Rate Limiting**: Add rate limiting to prevent abuse
3. **Caching**: Cache agent lists and categories for better performance
4. **Background Tasks**: Use Celery for long-running agent executions
5. **Logging**: Add comprehensive logging for debugging
6. **Monitoring**: Monitor webhook success rates and execution times
7. **Security**: Validate webhook responses and sanitize input data
This guide provides a complete, production-ready agents marketplace that can be easily integrated into any Django project.

View File

@ -1,969 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agent Frontend Template Prototype</title>
<style>
/* Agent Frontend Template - Common Components CSS */
@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 {
--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);
}
html {
scrollbar-gutter: stable;
}
body {
background: var(--background);
color: var(--on-surface);
line-height: 1.5;
font-weight: 400;
margin: 0;
padding: 0;
}
/* Layout */
.agent-container {
margin: 0 auto;
padding: var(--spacing-lg);
max-width: 1600px;
}
.agent-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-xl);
}
.header-controls {
display: flex;
align-items: center;
gap: var(--spacing-lg);
}
.agent-grid {
display: flex;
gap: var(--spacing-lg);
align-items: flex-start;
flex-wrap: wrap;
margin-bottom: var(--spacing-lg);
}
/* Typography */
.agent-title {
font-size: 32px;
font-weight: 700;
color: var(--on-surface);
margin: 0;
letter-spacing: -0.5px;
}
.agent-subtitle {
font-size: 16px;
color: var(--on-surface-variant);
margin: 4px 0 0 0;
}
/* Widgets */
.agent-widget {
background: var(--surface);
border-radius: var(--radius-lg);
padding: var(--spacing-xl);
border: 1px solid var(--outline-variant);
box-shadow: var(--shadow-sm);
transition: all 0.2s ease;
display: flex;
flex-direction: column;
}
.widget-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-lg);
padding-bottom: var(--spacing-md);
border-bottom: 1px solid var(--outline-variant);
}
.widget-title {
font-size: 18px;
font-weight: 600;
color: var(--on-surface);
margin: 0;
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);
}
.widget-content {
flex: 1;
display: flex;
flex-direction: column;
gap: var(--spacing-md);
}
/* Widget Sizes */
.widget-large {
flex: 1;
min-width: 400px;
}
.widget-small {
flex: 0 0 280px;
}
.widget-wide {
flex: 1 1 100%;
width: 100%;
}
/* Wallet Card */
.wallet-card {
background: linear-gradient(135deg, #000000 0%, #333333 100%);
color: white;
border-radius: var(--radius-md);
padding: var(--spacing-md);
border: none;
box-shadow: var(--shadow-md);
position: relative;
overflow: hidden;
margin-bottom: 0;
min-height: auto;
}
.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(30px, -30px);
}
.wallet-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-sm);
}
.wallet-title {
font-size: 14px;
font-weight: 600;
margin: 0;
opacity: 0.9;
}
.wallet-icon {
font-size: 20px;
}
.balance-display {
margin-bottom: 0;
}
.balance-amount {
font-size: 24px;
font-weight: 700;
line-height: 1;
margin-bottom: 0;
letter-spacing: -0.5px;
}
.balance-label {
font-size: 12px;
opacity: 0.8;
font-weight: 400;
}
.wallet-topup-btn {
width: 100%;
padding: 8px 16px;
background: linear-gradient(135deg, #4f46e5, #7c3aed);
color: white;
border: none;
border-radius: 8px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
margin-top: 12px;
}
.wallet-topup-btn:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
/* Processing Status */
.processing-status {
background: var(--surface);
border: 1px solid var(--outline-variant);
border-radius: var(--radius-lg);
padding: var(--spacing-xl);
text-align: center;
display: none;
}
.processing-status.active {
display: block;
}
.status-icon {
font-size: 48px;
margin-bottom: var(--spacing-md);
animation: pulse 2s infinite;
}
.status-title {
font-size: 18px;
font-weight: 600;
color: var(--on-surface);
margin-bottom: var(--spacing-sm);
}
.status-text {
font-size: 14px;
color: var(--on-surface-variant);
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Quick Agent Access Panel */
.quick-agent-toggle {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-md) var(--spacing-lg);
background: var(--surface);
border: 1px solid var(--outline-variant);
border-radius: var(--radius-md);
color: var(--on-surface);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
width: 100%;
justify-content: center;
}
.quick-agent-toggle:hover {
background: var(--surface-variant);
border-color: var(--primary);
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
.quick-agent-toggle.active {
background: var(--primary);
color: white;
border-color: var(--primary);
}
.toggle-icon {
font-size: 16px;
transition: transform 0.2s ease;
}
.quick-agent-toggle.active .toggle-icon {
transform: rotate(180deg);
}
.quick-agents-panel {
position: fixed;
top: 0;
right: 0;
width: min(400px, 90vw);
height: 100vh;
background: var(--surface);
border-left: 1px solid var(--outline);
box-shadow: var(--shadow-lg);
z-index: 1000;
transform: translateX(100%);
transition: transform 0.3s ease;
overflow-y: auto;
}
.quick-agents-panel.active {
transform: translateX(0);
}
.quick-agents-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-lg);
border-bottom: 1px solid var(--outline-variant);
background: var(--surface-variant);
}
.quick-agents-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--on-surface);
}
.close-panel {
background: none;
border: none;
font-size: 24px;
color: var(--on-surface-variant);
cursor: pointer;
padding: 4px;
border-radius: var(--radius-sm);
transition: all 0.2s ease;
}
.close-panel:hover {
background: var(--surface);
color: var(--on-surface);
}
.quick-agents-grid {
padding: var(--spacing-lg);
display: flex;
flex-direction: column;
gap: var(--spacing-md);
}
.quick-agent-card {
display: flex;
align-items: center;
gap: var(--spacing-md);
padding: var(--spacing-md);
border: 1px solid var(--outline-variant);
border-radius: var(--radius-md);
text-decoration: none;
color: var(--on-surface);
transition: all 0.2s ease;
background: var(--surface);
}
.quick-agent-card:hover {
background: var(--surface-variant);
border-color: var(--primary);
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
.agent-icon {
font-size: 24px;
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: var(--surface-variant);
border-radius: var(--radius-md);
flex-shrink: 0;
}
.agent-info {
flex: 1;
}
.agent-info h4 {
margin: 0 0 4px 0;
font-size: 14px;
font-weight: 600;
color: var(--on-surface);
}
.agent-info p {
margin: 0;
font-size: 12px;
color: var(--on-surface-variant);
line-height: 1.4;
}
.agent-price {
font-size: 12px;
font-weight: 600;
color: var(--primary);
background: rgba(0, 0, 0, 0.05);
padding: 2px 8px;
border-radius: var(--radius-sm);
margin-top: 4px;
display: inline-block;
}
.quick-agents-footer {
padding: var(--spacing-md) var(--spacing-lg);
border-top: 1px solid var(--outline-variant);
background: var(--surface-variant);
}
.view-all-agents {
display: block;
text-align: center;
padding: var(--spacing-md);
background: var(--primary);
color: white;
text-decoration: none;
border-radius: var(--radius-md);
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
}
.view-all-agents:hover {
background: #333333;
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
/* Overlay for mobile */
.quick-agents-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.quick-agents-overlay.active {
opacity: 1;
visibility: visible;
}
/* Placeholder Sections */
.placeholder-section {
background: var(--surface);
border: 2px dashed var(--outline);
border-radius: var(--radius-lg);
padding: var(--spacing-xl);
text-align: center;
color: var(--on-surface-variant);
min-height: 200px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-bottom: var(--spacing-lg);
}
.placeholder-title {
font-size: 18px;
font-weight: 600;
margin-bottom: var(--spacing-sm);
color: var(--on-surface);
}
.placeholder-description {
font-size: 14px;
line-height: 1.5;
max-width: 400px;
}
.placeholder-example {
background: var(--surface-variant);
border-radius: var(--radius-sm);
padding: var(--spacing-sm);
margin-top: var(--spacing-md);
font-size: 12px;
font-family: monospace;
color: var(--on-surface-variant);
}
/* Info List - For How It Works */
.info-list {
list-style: none;
padding: 0;
margin: 0;
counter-reset: step-counter;
}
.info-list li {
padding: var(--spacing-sm) 0;
color: var(--on-surface-variant);
font-size: 14px;
border-bottom: 1px solid var(--outline-variant);
position: relative;
padding-left: var(--spacing-lg);
}
.info-list li:last-child {
border-bottom: none;
}
.info-list li::before {
content: counter(step-counter);
counter-increment: step-counter;
position: absolute;
left: 0;
top: var(--spacing-sm);
background: var(--primary);
color: white;
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
}
/* Toast Notifications */
.toast {
position: fixed;
top: 20px;
right: 20px;
background: var(--surface);
border: 1px solid var(--outline);
border-radius: var(--radius-md);
padding: var(--spacing-md) var(--spacing-lg);
box-shadow: var(--shadow-lg);
z-index: 1000;
max-width: 400px;
font-size: 14px;
font-weight: 500;
transform: translateX(100%);
transition: transform 0.3s ease;
}
.toast.show {
transform: translateX(0);
}
.toast.success {
border-color: var(--success);
background: #f0fdf4;
color: #16a34a;
}
.toast.error {
border-color: var(--error);
background: #fef2f2;
color: #dc2626;
}
/* Responsive Design */
@media (max-width: 768px) {
.agent-container {
padding: var(--spacing-md);
}
.agent-header {
flex-direction: column;
align-items: flex-start;
gap: var(--spacing-sm);
}
.header-controls {
width: 100%;
justify-content: space-between;
}
.agent-title {
font-size: 24px;
}
.agent-grid {
flex-direction: column;
gap: var(--spacing-md);
}
.widget-large,
.widget-small {
min-width: auto;
flex: none;
}
.balance-amount {
font-size: 20px;
}
.quick-agents-panel {
width: 100%;
max-width: 100%;
right: 0;
left: 0;
}
}
</style>
</head>
<body>
<div class="agent-container">
<!-- Agent Header Component -->
<div class="agent-header">
<div>
<h1 class="agent-title">Sample Agent Title</h1>
<p class="agent-subtitle">This is the agent subtitle describing what this agent does</p>
</div>
<div class="header-controls">
<!-- Wallet Card Component -->
<div class="wallet-card widget-small">
<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">25.50</span> AED
</div>
<div class="balance-label">Available Balance</div>
</div>
<button type="button" class="wallet-topup-btn" onclick="showToast('Top-up feature demo!', 'success')">
💳 Top Up Wallet
</button>
</div>
</div>
</div>
<!-- Quick Agent Access Panel Overlay -->
<div class="quick-agents-overlay" id="quickAgentsOverlay" onclick="closeQuickAgents()"></div>
<!-- Quick Agent Access Panel -->
<div class="quick-agents-panel" id="quickAgentsPanel">
<div class="quick-agents-header">
<h3>Quick Access</h3>
<button class="close-panel" onclick="closeQuickAgents()" aria-label="Close panel">×</button>
</div>
<div class="quick-agents-grid">
<!-- Sample Agent Cards -->
<a href="#" class="quick-agent-card">
<div class="agent-icon">🌤️</div>
<div class="agent-info">
<h4>Weather Reporter</h4>
<p>Get real-time weather data</p>
<span class="agent-price">2.00 AED</span>
</div>
</a>
<a href="#" 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.00 AED</span>
</div>
</a>
<a href="#" class="quick-agent-card">
<div class="agent-icon">💼</div>
<div class="agent-info">
<h4>Job Posting Generator</h4>
<p>Create professional job postings</p>
<span class="agent-price">4.00 AED</span>
</div>
</a>
</div>
<div class="quick-agents-footer">
<a href="#" class="view-all-agents">View All Agents</a>
</div>
</div>
<!-- Agent Grid - PLACEHOLDER FOR AGENT-SPECIFIC CONTENT -->
<div class="agent-grid">
<div class="placeholder-section widget-large" style="flex: 1; margin-right: var(--spacing-lg);">
<div class="placeholder-title">🎯 Agent Grid Section</div>
<div class="placeholder-description">
This section is where each agent will place their unique form inputs, configuration options, and interaction elements.
</div>
<div class="placeholder-example">
Examples: File upload forms, text inputs, dropdowns, radio buttons, etc.
</div>
</div>
<!-- How It Works Widget - Common Pattern -->
<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">
<ol class="info-list">
<li>Enter your requirements</li>
<li>Configure options</li>
<li>Process with AI</li>
<li>Get results instantly</li>
</ol>
<!-- Quick Agents Toggle Button -->
<button class="quick-agent-toggle" onclick="toggleQuickAgents()"
title="Quick access to other agents"
aria-label="Open quick access panel for other AI agents"
aria-expanded="false"
aria-controls="quickAgentsPanel"
style="margin-top: var(--spacing-md);">
<span class="toggle-icon" aria-hidden="true">🚀</span>
<span class="toggle-text">Explore Other Agents</span>
</button>
</div>
</div>
</div>
<!-- Processing Status Component -->
<div class="agent-grid">
<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">Processing your request...</div>
<div class="status-text" id="statusText">Please wait while we analyze your data...</div>
</div>
</div>
</div>
<!-- Results Section - PLACEHOLDER FOR AGENT-SPECIFIC RESULTS -->
<div class="agent-grid">
<div class="placeholder-section widget-wide">
<div class="placeholder-title">📊 Results Section</div>
<div class="placeholder-description">
This section is where each agent will display their specific results format, content structure, and action buttons.
</div>
<div class="placeholder-example">
Examples: Formatted reports, analysis results, generated content, download buttons, etc.
</div>
</div>
</div>
<!-- Demo Controls -->
<div class="agent-grid" style="margin-top: var(--spacing-xl); border-top: 1px solid var(--outline-variant); padding-top: var(--spacing-lg);">
<div class="agent-widget widget-wide">
<div class="widget-header">
<h3 class="widget-title">
<span class="widget-icon">🧪</span>
Demo Controls
</h3>
</div>
<div class="widget-content">
<p style="margin-bottom: var(--spacing-md); color: var(--on-surface-variant);">
Test the common components and interactions:
</p>
<div style="display: flex; gap: var(--spacing-md); flex-wrap: wrap;">
<button onclick="showProcessing()" style="padding: var(--spacing-sm) var(--spacing-md); background: var(--primary); color: white; border: none; border-radius: var(--radius-sm); cursor: pointer;">
Show Processing
</button>
<button onclick="hideProcessing()" style="padding: var(--spacing-sm) var(--spacing-md); background: var(--surface); color: var(--on-surface); border: 1px solid var(--outline); border-radius: var(--radius-sm); cursor: pointer;">
Hide Processing
</button>
<button onclick="showToast('Success message!', 'success')" style="padding: var(--spacing-sm) var(--spacing-md); background: var(--success); color: white; border: none; border-radius: var(--radius-sm); cursor: pointer;">
Success Toast
</button>
<button onclick="showToast('Error message!', 'error')" style="padding: var(--spacing-sm) var(--spacing-md); background: var(--error); color: white; border: none; border-radius: var(--radius-sm); cursor: pointer;">
Error Toast
</button>
</div>
</div>
</div>
</div>
</div>
<script>
// Agent Frontend Template - Common JavaScript Utilities
// Quick Agent Access Panel Functions
function toggleQuickAgents() {
const panel = document.getElementById('quickAgentsPanel');
const overlay = document.getElementById('quickAgentsOverlay');
const toggle = document.querySelector('.quick-agent-toggle');
const isActive = panel.classList.contains('active');
if (isActive) {
// Close panel
panel.classList.remove('active');
overlay.classList.remove('active');
toggle.classList.remove('active');
toggle.setAttribute('aria-expanded', 'false');
panel.setAttribute('aria-hidden', 'true');
overlay.setAttribute('aria-hidden', 'true');
document.body.style.overflow = 'auto';
} else {
// Open panel
panel.classList.add('active');
overlay.classList.add('active');
toggle.classList.add('active');
toggle.setAttribute('aria-expanded', 'true');
panel.setAttribute('aria-hidden', 'false');
overlay.setAttribute('aria-hidden', 'false');
document.body.style.overflow = 'hidden';
}
}
function closeQuickAgents() {
const panel = document.getElementById('quickAgentsPanel');
const overlay = document.getElementById('quickAgentsOverlay');
const toggle = document.querySelector('.quick-agent-toggle');
if (panel && overlay && toggle) {
panel.classList.remove('active');
overlay.classList.remove('active');
toggle.classList.remove('active');
toggle.setAttribute('aria-expanded', 'false');
panel.setAttribute('aria-hidden', 'true');
overlay.setAttribute('aria-hidden', 'true');
document.body.style.overflow = 'auto';
}
}
// Toast Notification Function
function showToast(message, type = 'info') {
// Remove existing toasts
document.querySelectorAll('.toast').forEach(toast => toast.remove());
// Create new toast
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.textContent = message;
// Add to page
document.body.appendChild(toast);
// Show toast with animation
setTimeout(() => toast.classList.add('show'), 100);
// Auto remove after 3 seconds
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
// Processing Status Functions
function showProcessing() {
const processingStatus = document.getElementById('processingStatus');
processingStatus.style.display = 'block';
processingStatus.classList.add('active');
showToast('Processing started...', 'success');
}
function hideProcessing() {
const processingStatus = document.getElementById('processingStatus');
processingStatus.style.display = 'none';
processingStatus.classList.remove('active');
showToast('Processing stopped...', 'success');
}
// Wallet Balance Update Function
function updateWalletBalance(newBalance) {
if (newBalance !== undefined) {
const walletBalance = document.getElementById('walletBalance');
if (walletBalance) {
walletBalance.textContent = newBalance.toFixed(2);
}
showToast(`Wallet updated: ${newBalance.toFixed(2)} AED`, 'success');
}
}
// Copy to Clipboard Utility
function copyToClipboard(text, successMessage = 'Copied to clipboard!') {
navigator.clipboard.writeText(text).then(() => {
showToast(`📋 ${successMessage}`, 'success');
}).catch(() => {
showToast('Failed to copy to clipboard', 'error');
});
}
// Download as File Utility
function 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);
showToast(`💾 ${successMessage}`, 'success');
}
// Close panel on Escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeQuickAgents();
}
});
// Initialize accessibility features
document.addEventListener('DOMContentLoaded', function() {
// Set initial ARIA states
const quickAgentsButton = document.querySelector('.quick-agent-toggle');
if (quickAgentsButton) {
quickAgentsButton.setAttribute('aria-expanded', 'false');
}
const panel = document.getElementById('quickAgentsPanel');
const overlay = document.getElementById('quickAgentsOverlay');
if (panel) panel.setAttribute('aria-hidden', 'true');
if (overlay) overlay.setAttribute('aria-hidden', 'true');
// Show welcome message
setTimeout(() => {
showToast('Agent template prototype loaded successfully!', 'success');
}, 500);
});
// Placeholder for agent-specific JavaScript
// ========================================
//
// Agent-specific functions would go here:
// - Form validation
// - AJAX requests
// - Result processing
// - Custom interactions
//
// Example structure:
// function validateAgentForm() { ... }
// function submitAgentRequest() { ... }
// function displayAgentResults() { ... }
//
// ========================================
</script>
</body>
</html>

View File

@ -1,17 +0,0 @@
=== Documentation Auto-Update Summary ===
Update Date: 2025-08-01 09:23:11
Recent Commits:
- 11d28a1 📚 Update documentation after GitHub push
- f6970b6 🎨 Complete Phase 1 UI optimization with button hover fixes
- 277e7ec 📄 Auto-update documentation timestamp after security fixes
Documentation Changes:
- CLAUDE.md
Backend Changes:
- docs_update_summary.txt
No documentation files required updates.
=== End Summary ===

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,652 +0,0 @@
/* Digital Branding Services Page Styles */
/* Following homepage.css patterns and using unified color system from base.css */
/* Hero Section - matches homepage hero */
.digital-branding-hero {
position: relative;
background: var(--gradient-hero);
overflow: hidden;
min-height: 85vh;
display: flex;
align-items: center;
padding: clamp(40px, 10vw, 80px) clamp(16px, 4vw, 24px) clamp(60px, 15vw, 100px);
}
.hero-container {
max-width: 1280px;
margin: 0 auto;
text-align: center;
position: relative;
z-index: 10;
width: 100%;
}
.trust-badge {
display: inline-flex;
align-items: center;
gap: 0.5rem;
background: rgba(59, 130, 246, 0.08);
padding: 0.5rem 1.25rem;
border-radius: 50px;
margin-bottom: 2.5rem;
font-size: 0.875rem;
font-weight: 600;
color: var(--primary-blue);
border: 1px solid rgba(59, 130, 246, 0.15);
backdrop-filter: blur(10px);
}
.trust-badge-emoji {
font-size: 0.75rem;
}
.hero-title {
font-weight: 800;
margin-bottom: 2.5rem;
line-height: 1.1;
letter-spacing: -0.02em;
font-size: clamp(36px, 8vw, 110px);
margin-bottom: clamp(24px, 6vw, 40px);
}
.hero-title-gradient {
background: var(--gradient-primary);
background-size: 300% 300%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-title-normal {
color: var(--text-primary);
}
.hero-description {
color: var(--text-secondary);
margin-bottom: 3.5rem;
max-width: 720px;
margin-left: auto;
margin-right: auto;
line-height: 1.7;
font-weight: 400;
font-size: clamp(1.1rem, 3vw, 1.4rem);
margin-bottom: clamp(32px, 8vw, 56px);
padding: 0 clamp(8px, 2vw, 16px);
}
.hero-buttons {
display: flex;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 5rem;
gap: clamp(12px, 3vw, 20px);
margin-bottom: clamp(40px, 10vw, 80px);
padding: 0 clamp(8px, 2vw, 16px);
}
.btn-primary {
background: var(--gradient-primary);
color: white;
border-radius: 1rem;
font-weight: bold;
border: none;
cursor: pointer;
box-shadow: 0 8px 24px rgba(59, 130, 246, 0.25), 0 4px 12px rgba(0, 0, 0, 0.05);
letter-spacing: 0.01em;
text-align: center;
text-decoration: none;
display: inline-block;
padding: clamp(14px, 4vw, 18px) clamp(24px, 6vw, 36px);
font-size: clamp(14px, 3.5vw, 18px);
min-height: 48px;
min-width: clamp(140px, 40vw, 180px);
transition: all 0.2s ease;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(59, 130, 246, 0.4);
filter: brightness(1.1);
}
.btn-primary:active {
transform: translateY(0);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.2);
}
.btn-secondary {
background: var(--background-card);
color: var(--primary-blue);
border-radius: 1rem;
font-weight: 600;
border: 2px solid var(--border-light);
text-decoration: none;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.04), 0 4px 12px rgba(59, 130, 246, 0.08);
letter-spacing: 0.01em;
display: inline-block;
text-align: center;
padding: clamp(14px, 4vw, 18px) clamp(24px, 6vw, 36px);
font-size: clamp(14px, 3.5vw, 18px);
min-height: 48px;
min-width: clamp(140px, 40vw, 180px);
transition: all 0.2s ease;
}
.btn-secondary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(59, 130, 246, 0.15);
border-color: var(--primary-blue);
background: rgba(59, 130, 246, 0.05);
}
.btn-secondary:active {
transform: translateY(0);
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.12);
}
.trust-indicators {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr));
gap: clamp(16px, 4vw, 48px);
max-width: 600px;
margin: 0 auto;
padding: 0 clamp(8px, 2vw, 16px);
}
.trust-card {
text-align: center;
background: rgba(255, 255, 255, 0.5);
border: 1px solid rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.03);
border-radius: 1rem;
padding: clamp(16px, 4vw, 24px) clamp(12px, 3vw, 16px);
}
.trust-number {
background: var(--text-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 800;
margin-bottom: 0.5rem;
font-size: clamp(1rem, 3vw, 1.2rem);
}
.trust-text {
color: var(--text-secondary);
font-weight: 500;
letter-spacing: 0.01em;
font-size: clamp(12px, 3vw, 15px);
}
/* Why Choose Us Section - matches company profile */
.why-choose-us {
position: relative;
background: var(--company-gradient);
overflow: hidden;
padding: clamp(60px, 15vw, 120px) clamp(16px, 4vw, 24px);
}
.section-container {
max-width: 1200px;
margin: 0 auto;
position: relative;
z-index: 10;
}
.section-header {
text-align: center;
margin-bottom: clamp(40px, 10vw, 80px);
}
.section-badge {
display: inline-flex;
align-items: center;
gap: 0.5rem;
background: rgba(30, 64, 175, 0.1);
padding: 0.5rem 1.25rem;
border-radius: 50px;
margin-bottom: 1.5rem;
border: 1px solid rgba(30, 64, 175, 0.2);
}
.section-badge-icon {
font-size: 1rem;
}
.section-badge-text {
font-size: 0.875rem;
font-weight: 600;
color: var(--primary-blue);
}
.section-title {
font-weight: 800;
color: var(--primary-blue);
margin-bottom: 1rem;
text-align: center;
font-size: clamp(2rem, 5vw, 3.5rem);
}
.section-subtitle {
font-size: 1.25rem;
color: var(--text-light);
max-width: 600px;
margin: 0 auto;
line-height: 1.6;
}
.why-choose-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
gap: clamp(24px, 6vw, 40px);
}
.choice-card {
background: white;
border-radius: clamp(16px, 4vw, 24px);
box-shadow: 0 8px 32px rgba(30, 64, 175, 0.08);
text-align: center;
padding: clamp(24px, 6vw, 40px);
transition: all 0.2s ease;
}
.choice-icon {
margin-bottom: 1.25rem;
font-size: clamp(2.5rem, 6vw, 3.5rem);
margin-bottom: clamp(16px, 4vw, 20px);
}
.choice-title {
font-weight: bold;
color: var(--primary-blue);
margin-bottom: 1rem;
font-size: clamp(1.2rem, 4vw, 1.5rem);
margin-bottom: clamp(12px, 3vw, 16px);
}
.choice-description {
color: var(--text-light);
line-height: 1.6;
font-size: clamp(14px, 3.5vw, 18px);
}
/* Our Process Section - matches services */
.our-process {
background: var(--services-gradient);
padding: clamp(60px, 15vw, 120px) clamp(16px, 4vw, 24px);
}
.process-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
gap: clamp(20px, 5vw, 32px);
margin-bottom: clamp(40px, 10vw, 60px);
}
.process-step-card {
background: white;
border-radius: clamp(16px, 4vw, 20px);
box-shadow: 0 8px 32px rgba(30, 64, 175, 0.08);
padding: clamp(24px, 6vw, 32px);
display: flex;
align-items: flex-start;
gap: clamp(16px, 4vw, 20px);
transition: all 0.2s ease;
}
.step-number {
background: var(--gradient-primary);
color: white;
width: clamp(40px, 10vw, 50px);
height: clamp(40px, 10vw, 50px);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: clamp(16px, 4vw, 20px);
flex-shrink: 0;
}
.step-content {
flex: 1;
}
.step-title {
font-weight: bold;
color: var(--primary-blue);
margin-bottom: 0.75rem;
font-size: clamp(1.1rem, 3.5vw, 1.3rem);
}
.step-description {
color: var(--text-light);
line-height: 1.6;
font-size: clamp(14px, 3.5vw, 16px);
}
/* RACE Framework */
.race-framework {
background: white;
border-radius: clamp(16px, 4vw, 24px);
box-shadow: 0 12px 40px rgba(30, 64, 175, 0.08);
padding: clamp(24px, 6vw, 40px);
}
.race-title {
text-align: center;
font-weight: bold;
color: var(--primary-blue);
margin-bottom: clamp(20px, 5vw, 32px);
font-size: clamp(1.3rem, 4vw, 1.8rem);
}
.race-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr));
gap: clamp(16px, 4vw, 24px);
}
.race-card {
text-align: center;
background: var(--background-light);
border-radius: clamp(12px, 3vw, 16px);
padding: clamp(16px, 4vw, 24px);
transition: all 0.2s ease;
}
.race-phase {
margin-bottom: 0.75rem;
}
.race-icon {
font-size: clamp(1.5rem, 4vw, 2rem);
margin-bottom: 0.5rem;
display: block;
}
.race-phase h4 {
font-weight: bold;
color: var(--primary-blue);
margin: 0;
font-size: clamp(1.1rem, 3.5vw, 1.3rem);
}
.race-focus {
font-weight: 600;
color: var(--text-primary);
margin-bottom: 0.5rem;
font-size: clamp(14px, 3.5vw, 16px);
}
.race-actions {
color: var(--text-light);
font-size: clamp(12px, 3vw, 14px);
line-height: 1.5;
}
/* Branding Services Section - matches services */
.branding-services {
background: var(--clients-gradient);
padding: clamp(60px, 15vw, 120px) clamp(16px, 4vw, 24px);
}
.services-title {
font-weight: bold;
text-align: center;
margin-bottom: 1rem;
color: var(--primary-blue);
font-size: clamp(1.8rem, 5vw, 2.5rem);
}
.services-subtitle {
text-align: center;
color: var(--text-light);
margin-bottom: 3rem;
font-size: clamp(16px, 4vw, 20px);
margin-bottom: clamp(24px, 6vw, 48px);
}
.branding-services-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
gap: clamp(20px, 5vw, 32px);
}
.service-card {
background: white;
border-radius: clamp(16px, 4vw, 20px);
box-shadow: 0 8px 32px rgba(30, 64, 175, 0.08);
text-align: center;
padding: clamp(24px, 6vw, 32px);
transition: all 0.2s ease;
}
.service-icon {
margin-bottom: 1.25rem;
font-size: clamp(2.5rem, 6vw, 3rem);
margin-bottom: clamp(16px, 4vw, 20px);
}
.service-title {
font-weight: bold;
color: var(--primary-blue);
margin-bottom: 1rem;
font-size: clamp(1.2rem, 4vw, 1.4rem);
margin-bottom: clamp(12px, 3vw, 16px);
}
.service-description {
color: var(--text-light);
line-height: 1.6;
font-size: clamp(14px, 3.5vw, 16px);
}
/* CTA Section - matches contact */
.branding-cta {
background: var(--contact-gradient);
padding: clamp(60px, 15vw, 120px) clamp(16px, 4vw, 24px);
}
.cta-container {
max-width: 1200px;
margin: 0 auto;
}
.cta-title {
font-weight: bold;
text-align: center;
color: var(--primary-blue);
font-size: clamp(1.8rem, 5vw, 2.5rem);
margin-bottom: clamp(24px, 6vw, 48px);
}
.cta-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(350px, 100%), 1fr));
gap: clamp(30px, 8vw, 60px);
align-items: start;
}
.cta-content {
background: white;
border-radius: clamp(16px, 4vw, 20px);
box-shadow: 0 8px 32px rgba(30, 64, 175, 0.08);
padding: clamp(24px, 6vw, 40px);
}
.cta-content-title {
font-weight: bold;
color: var(--primary-blue);
margin-bottom: 1rem;
font-size: clamp(1.3rem, 4vw, 1.6rem);
}
.cta-description {
color: var(--text-light);
line-height: 1.6;
margin-bottom: 1.5rem;
font-size: clamp(14px, 3.5vw, 18px);
}
.cta-features {
margin-bottom: 2rem;
}
.cta-feature {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 0.75rem;
font-size: clamp(14px, 3.5vw, 16px);
}
.feature-icon {
color: var(--success-green);
font-weight: bold;
}
.cta-buttons {
display: flex;
flex-direction: column;
gap: clamp(12px, 3vw, 16px);
}
.cta-btn {
padding: clamp(14px, 4vw, 16px) clamp(20px, 5vw, 24px);
border-radius: clamp(8px, 2vw, 12px);
font-weight: 600;
text-decoration: none;
text-align: center;
font-size: clamp(14px, 3.5vw, 16px);
transition: all 0.2s ease;
min-height: 48px;
display: flex;
align-items: center;
justify-content: center;
}
.cta-btn.primary {
background: var(--gradient-primary);
color: white;
box-shadow: 0 4px 16px rgba(59, 130, 246, 0.25);
}
.cta-btn.primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(59, 130, 246, 0.35);
}
.cta-btn.secondary {
background: var(--background-card);
color: var(--primary-blue);
border: 2px solid var(--border-light);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.05);
}
.cta-btn.secondary:hover {
transform: translateY(-2px);
border-color: var(--primary-blue);
background: rgba(59, 130, 246, 0.05);
box-shadow: 0 6px 20px rgba(59, 130, 246, 0.15);
}
.cta-info {
background: white;
border-radius: clamp(16px, 4vw, 20px);
box-shadow: 0 8px 32px rgba(30, 64, 175, 0.08);
padding: clamp(24px, 6vw, 40px);
}
.cta-info-title {
font-weight: bold;
color: var(--primary-blue);
margin-bottom: 1.5rem;
font-size: clamp(1.2rem, 4vw, 1.5rem);
margin-bottom: clamp(16px, 4vw, 24px);
}
.contact-item {
display: flex;
align-items: flex-start;
gap: clamp(12px, 3vw, 16px);
margin-bottom: clamp(20px, 5vw, 32px);
}
.contact-icon {
background: var(--primary-gradient);
border-radius: clamp(8px, 2vw, 12px);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
width: clamp(40px, 10vw, 50px);
height: clamp(40px, 10vw, 50px);
font-size: clamp(16px, 4vw, 20px);
}
.contact-details h4 {
font-weight: 600;
color: var(--primary-blue);
margin-bottom: 0.5rem;
font-size: clamp(16px, 4vw, 19px);
}
.contact-details p {
color: var(--text-light);
line-height: 1.6;
font-size: clamp(14px, 3.5vw, 16px);
}
.contact-email {
color: var(--primary-blue);
text-decoration: none;
}
.contact-email:hover {
text-decoration: underline;
}
.commitment-statement {
background: var(--services-gradient);
border-left: 4px solid var(--primary-blue);
border-radius: clamp(12px, 3vw, 16px);
padding: clamp(16px, 4vw, 24px);
margin-top: clamp(20px, 5vw, 32px);
}
.commitment-text {
color: var(--primary-blue);
font-weight: 600;
text-align: center;
font-size: clamp(14px, 3.5vw, 18px);
line-height: 1.6;
}
/* Loading state for buttons */
.cta-btn.loading {
opacity: 0.7;
pointer-events: none;
}
/* Mobile responsive adjustments */
@media (max-width: 767px) {
.cta-buttons {
flex-direction: column;
}
.process-step-card {
flex-direction: column;
text-align: center;
}
.step-number {
margin: 0 auto 1rem auto;
}
.race-grid {
grid-template-columns: 1fr;
}
}

View File

@ -1,645 +0,0 @@
/**
* Data Analyzer - Agent-Specific JavaScript
* Handles unique functionality for Data Analyzer agent
* Uses WorkflowsCore architecture like other agents
*/
class DataAnalyzerProcessor extends WorkflowsCore {
constructor() {
super();
this.agentSlug = 'data-analyzer';
this.webhookUrl = 'http://localhost:5678/webhook/simple-pdf-processor';
this.price = 8.0; // Will be overridden by template data
this.sessionId = this.constructor.generateSessionId();
// Initialize on page load
this.initialize();
}
initialize() {
// Set data attributes from page
const priceElement = document.body.getAttribute('data-agent-price');
if (priceElement) {
this.price = parseFloat(priceElement);
}
// Initialize form submission
const form = document.getElementById('agentForm');
if (form) {
form.addEventListener('submit', this.handleFormSubmission.bind(this));
}
// Initialize file upload functionality
this.initializeFileUpload();
// Initialize form validation
this.initializeFormValidation();
// Set initial radio selection
const firstRadio = document.querySelector('.radio-card');
if (firstRadio && !document.querySelector('.radio-card.selected')) {
firstRadio.classList.add('selected');
const input = firstRadio.querySelector('input[type="radio"]');
if (input) input.checked = true;
}
}
/**
* Initialize file upload functionality
*/
initializeFileUpload() {
const fileInput = document.getElementById('dataFile');
if (fileInput) {
fileInput.addEventListener('change', this.handleFileChange.bind(this));
}
// Initialize drag and drop
const uploadArea = document.querySelector('.file-upload-area');
if (uploadArea && fileInput) {
this.constructor.setupDragAndDrop(uploadArea, fileInput);
}
}
/**
* Handle form submission with hybrid N8N/Django approach
*/
async handleFormSubmission(e) {
e.preventDefault();
if (!this.isFormValid()) {
this.constructor.showToast('Please upload a file and select analysis type', 'error');
return;
}
// Check authentication and balance
if (!this.constructor.checkAuthentication()) return;
if (!this.constructor.checkBalance(this.price)) return;
// Show processing status and disable submit button
this.constructor.showProcessing('Analyzing your data file...');
const submitBtn = document.getElementById('generateBtn');
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.textContent = '⏳ Analyzing...';
}
try {
// Try direct N8N integration for better performance (with Django fallback)
const useDirectN8N = false; // Feature flag - disabled for file uploads (complex)
if (useDirectN8N) {
await this.processViaDirectN8N(e.target);
} else {
// For file uploads, use immediate Django processing (N8N direct upload is complex)
await this.processViaDjangoImmediate(e.target);
}
} catch (error) {
console.error('Form submission error:', error);
this.constructor.hideProcessing();
this.constructor.showToast('❌ Connection error. Please try again.', 'error');
this.resetSubmitButton();
}
}
/**
* Django processing for file uploads (immediate response for data analyzer)
*/
async processViaDjangoImmediate(form) {
const formData = new FormData(form);
const response = await fetch(window.location.href, {
method: 'POST',
body: formData,
headers: { 'X-Requested-With': 'XMLHttpRequest' }
});
const result = await response.json();
if (result.success && result.analysis_results) {
// Data analyzer returns results immediately, no polling needed
this.constructor.hideProcessing();
if (result.wallet_balance !== undefined) {
this.constructor.updateWalletBalance(result.wallet_balance);
}
// Display results immediately
const analysisData = result.analysis_results;
const formattedHtml = this.formatAnalysisResults(analysisData);
WorkflowsCore.showResults(formattedHtml, 'Analysis Results');
this.constructor.showToast('✅ Data analysis completed successfully!', 'success');
this.resetSubmitButton();
} else {
this.constructor.hideProcessing();
this.constructor.showToast(`${result.error || 'Processing failed'}`, 'error');
this.resetSubmitButton();
}
}
/**
* Form validation specific to Data Analyzer
*/
initializeFormValidation() {
const fileInput = document.getElementById('dataFile');
const analysisTypeInputs = document.querySelectorAll('input[name="analysisType"]');
if (fileInput) {
fileInput.addEventListener('change', () => this.validateField('dataFile'));
}
analysisTypeInputs.forEach(input => {
input.addEventListener('change', () => this.validateField('analysisType'));
});
}
validateField(fieldName) {
switch (fieldName) {
case 'dataFile':
const fileInput = document.getElementById('dataFile');
if (!fileInput.files || fileInput.files.length === 0) {
this.constructor.showFieldError('dataFile', 'Please select a data file');
return false;
}
const file = fileInput.files[0];
const maxSize = 10 * 1024 * 1024; // 10MB
if (file.size > maxSize) {
this.constructor.showFieldError('dataFile', 'File too large. Maximum size is 10MB');
return false;
}
const allowedExtensions = ['.pdf'];
const fileExtension = '.' + file.name.split('.').pop().toLowerCase();
if (!allowedExtensions.includes(fileExtension)) {
this.constructor.showFieldError('dataFile', 'Unsupported file type. Please use PDF files only');
return false;
}
break;
case 'analysisType':
const analysisType = document.querySelector('input[name="analysisType"]:checked');
if (!analysisType) {
this.constructor.showFieldError('analysisType', 'Please select an analysis type');
return false;
}
break;
}
this.constructor.clearFieldError(fieldName);
return true;
}
isFormValid() {
const fileValid = this.validateField('dataFile');
const analysisValid = this.validateField('analysisType');
return fileValid && analysisValid;
}
/**
* Handle file change events with enhanced UX
*/
handleFileChange(event) {
const file = event.target.files[0];
const uploadArea = document.getElementById('fileUploadArea');
const filePreview = document.getElementById('filePreview');
const validationMessage = document.getElementById('validationMessage');
if (file) {
// Validate file first
const validation = this.validateFileUpload(file);
if (!validation.valid) {
this.showValidationMessage(validation.message, 'error');
uploadArea.classList.add('upload-error');
uploadArea.classList.remove('file-selected');
this.hideFilePreview();
return;
}
// Show success validation
this.showValidationMessage(validation.message, 'success');
// Update upload area
uploadArea.classList.remove('upload-error');
uploadArea.classList.add('file-selected');
// Show file preview with enhanced info
this.showFilePreview(file);
// Clear any previous errors
this.constructor.clearFieldError('dataFile');
} else {
// Reset all states
this.resetFileUploadState();
}
}
/**
* Validate file upload with detailed feedback
*/
validateFileUpload(file) {
const maxSize = 10 * 1024 * 1024; // 10MB
const allowedTypes = ['application/pdf'];
const allowedExtensions = ['.pdf'];
// Check file type
if (!allowedTypes.includes(file.type) && !allowedExtensions.includes('.' + file.name.split('.').pop().toLowerCase())) {
return {
valid: false,
message: 'Invalid file type. Please upload a PDF file only.'
};
}
// Check file size
if (file.size > maxSize) {
return {
valid: false,
message: `File too large (${this.formatFileSize(file.size)}). Maximum size is 10MB.`
};
}
// Check for empty file
if (file.size === 0) {
return {
valid: false,
message: 'File appears to be empty. Please select a valid PDF file.'
};
}
return {
valid: true,
message: `✅ File validated successfully (${this.formatFileSize(file.size)})`
};
}
/**
* Show file preview with enhanced information
*/
showFilePreview(file) {
const filePreview = document.getElementById('filePreview');
const previewFileName = document.getElementById('previewFileName');
const previewFileSize = document.getElementById('previewFileSize');
const previewTimestamp = document.getElementById('previewTimestamp');
if (filePreview && previewFileName && previewFileSize && previewTimestamp) {
previewFileName.textContent = file.name;
previewFileSize.textContent = this.formatFileSize(file.size);
previewTimestamp.textContent = `Added ${new Date().toLocaleTimeString()}`;
filePreview.classList.add('show');
}
}
/**
* Hide file preview
*/
hideFilePreview() {
const filePreview = document.getElementById('filePreview');
if (filePreview) {
filePreview.classList.remove('show');
}
}
/**
* Show validation message with type
*/
showValidationMessage(message, type = 'info') {
const validationMessage = document.getElementById('validationMessage');
if (validationMessage) {
validationMessage.textContent = message;
validationMessage.className = `validation-message show ${type}`;
}
}
/**
* Hide validation message
*/
hideValidationMessage() {
const validationMessage = document.getElementById('validationMessage');
if (validationMessage) {
validationMessage.classList.remove('show');
}
}
/**
* Reset file upload state
*/
resetFileUploadState() {
const uploadArea = document.getElementById('fileUploadArea');
const filePreview = document.getElementById('filePreview');
if (uploadArea) {
uploadArea.classList.remove('file-selected', 'upload-error', 'uploading');
}
this.hideFilePreview();
this.hideValidationMessage();
}
/**
* Show upload progress
*/
showUploadProgress() {
const uploadProgress = document.getElementById('uploadProgress');
const progressFill = document.getElementById('progressFill');
const progressText = document.getElementById('progressText');
if (uploadProgress) {
uploadProgress.classList.add('show');
}
// Simulate progress for visual feedback
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 15;
if (progress > 90) progress = 90;
if (progressFill) progressFill.style.width = `${progress}%`;
if (progressText) progressText.textContent = `Uploading... ${Math.round(progress)}%`;
if (progress >= 90) {
clearInterval(interval);
if (progressText) progressText.textContent = 'Processing file...';
}
}, 200);
this.uploadProgressInterval = interval;
}
/**
* Hide upload progress
*/
hideUploadProgress() {
const uploadProgress = document.getElementById('uploadProgress');
if (uploadProgress) {
uploadProgress.classList.remove('show');
}
if (this.uploadProgressInterval) {
clearInterval(this.uploadProgressInterval);
this.uploadProgressInterval = null;
}
}
/**
* Format file size for display
*/
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];
}
/**
* Format analysis results for HTML display
*/
formatAnalysisResults(analysisData) {
let resultsHtml = '<h3>✅ Analysis Complete</h3>';
// Check if we have structured sections data
if (analysisData && typeof analysisData === 'object' && analysisData.sections && Array.isArray(analysisData.sections) && analysisData.sections.length > 0) {
resultsHtml += '<div class="analysis-sections">';
analysisData.sections.forEach(section => {
if (section.heading && section.content) {
resultsHtml += `
<div style="background: var(--surface-variant); border-radius: var(--radius-md); padding: var(--spacing-lg); margin-bottom: var(--spacing-md); border-left: 4px solid var(--primary);">
<h4 style="color: var(--primary); font-weight: 600; margin: 0 0 var(--spacing-md) 0; font-size: 16px;">📋 ${this.escapeHtml(section.heading)}</h4>
<div style="color: var(--on-surface); line-height: 1.6; font-size: 14px;">${this.escapeHtml(section.content).replace(/\n/g, '<br>')}</div>
</div>
`;
}
});
resultsHtml += '</div>';
} else {
// Handle simple text response or fallback
const content = typeof analysisData === 'string' ? analysisData : JSON.stringify(analysisData, null, 2);
resultsHtml += `
<div style="background: var(--surface-variant); border-radius: var(--radius-md); padding: var(--spacing-lg); margin-bottom: var(--spacing-md); border-left: 4px solid var(--primary);">
<h4 style="color: var(--primary); font-weight: 600; margin: 0 0 var(--spacing-md) 0; font-size: 16px;">📊 Analysis Results</h4>
<div style="color: var(--on-surface); line-height: 1.6; font-size: 14px; white-space: pre-wrap;">${this.escapeHtml(content)}</div>
</div>
`;
}
// Add timestamp
if (analysisData && analysisData.timestamp) {
resultsHtml += `<p style="margin-top: var(--spacing-lg); text-align: center; color: var(--on-surface-variant);"><small>Analysis completed: ${new Date(analysisData.timestamp).toLocaleString()}</small></p>`;
} else {
resultsHtml += `<p style="margin-top: var(--spacing-lg); text-align: center; color: var(--on-surface-variant);"><small>Analysis completed: ${new Date().toLocaleString()}</small></p>`;
}
return resultsHtml;
}
/**
* Escape HTML to prevent XSS
*/
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
/**
* Reset submit button to original state
*/
resetSubmitButton() {
const submitBtn = document.getElementById('generateBtn');
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.textContent = `🚀 Analyze Data (${this.price} AED)`;
}
}
}
// Data Analyzer specific functions (global for template onclick handlers)
function selectRadio(value) {
// Remove selected class from all cards
document.querySelectorAll('.radio-card').forEach(card => {
card.classList.remove('selected');
});
// Add selected class to clicked card
const selectedCard = document.querySelector(`input[value="${value}"]`).closest('.radio-card');
if (selectedCard) {
selectedCard.classList.add('selected');
}
// Select the radio button
const radioInput = document.getElementById(value);
if (radioInput) {
radioInput.checked = true;
}
}
// File management functions (global for template onclick handlers)
function triggerFileSelect() {
const fileInput = document.getElementById('dataFile');
if (fileInput) {
fileInput.click();
}
}
function replaceFile() {
const fileInput = document.getElementById('dataFile');
if (fileInput) {
fileInput.value = '';
fileInput.click();
}
}
function removeFile() {
const fileInput = document.getElementById('dataFile');
const uploadArea = document.getElementById('fileUploadArea');
const filePreview = document.getElementById('filePreview');
const validationMessage = document.getElementById('validationMessage');
if (fileInput) {
fileInput.value = '';
// Reset upload area
if (uploadArea) {
uploadArea.classList.remove('file-selected', 'upload-error');
}
// Hide preview and validation
if (filePreview) {
filePreview.classList.remove('show');
}
if (validationMessage) {
validationMessage.classList.remove('show');
}
// Clear any form errors
if (window.dataAnalyzerProcessor) {
window.dataAnalyzerProcessor.constructor.clearFieldError('dataFile');
}
}
}
// Drag and drop functionality
function setupDragAndDrop() {
const uploadArea = document.getElementById('fileUploadArea');
const fileInput = document.getElementById('dataFile');
if (!uploadArea || !fileInput) return;
// Prevent default drag behaviors
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
uploadArea.addEventListener(eventName, preventDefaults, false);
document.body.addEventListener(eventName, preventDefaults, false);
});
// Highlight drop area when item is dragged over it
['dragenter', 'dragover'].forEach(eventName => {
uploadArea.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
uploadArea.addEventListener(eventName, unhighlight, false);
});
// Handle dropped files
uploadArea.addEventListener('drop', handleDrop, false);
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
function highlight(e) {
uploadArea.classList.add('dragover');
}
function unhighlight(e) {
uploadArea.classList.remove('dragover');
}
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
if (files.length > 0) {
fileInput.files = files;
fileInput.dispatchEvent(new Event('change', { bubbles: true }));
}
}
}
// Result action functions (global for button onclick handlers)
function copyResults() {
const content = document.getElementById('resultsContent');
if (content) {
const text = content.textContent || '';
WorkflowsCore.copyToClipboard(text, 'Analysis results copied to clipboard!');
}
}
function downloadResults() {
const content = document.getElementById('resultsContent');
if (content) {
const text = content.textContent || '';
WorkflowsCore.downloadAsFile(text, 'data-analysis-results.txt', 'Analysis results downloaded!');
}
}
function resetForm() {
const form = document.getElementById('agentForm');
if (form) {
form.reset();
}
const resultsContainer = document.getElementById('resultsContainer');
const processingStatus = document.getElementById('processingStatus');
if (resultsContainer) resultsContainer.style.display = 'none';
if (processingStatus) processingStatus.style.display = 'none';
// Clear file display
const fileNameDisplay = document.getElementById('fileName');
const fileSizeDisplay = document.getElementById('fileSize');
if (fileNameDisplay) {
fileNameDisplay.textContent = '';
fileNameDisplay.style.display = 'none';
}
if (fileSizeDisplay) {
fileSizeDisplay.textContent = '';
fileSizeDisplay.style.display = 'none';
}
// Clear validation errors
WorkflowsCore.clearFieldError('dataFile');
WorkflowsCore.clearFieldError('analysisType');
// Reset radio selection
const firstRadio = document.querySelector('.radio-card');
if (firstRadio) {
document.querySelectorAll('.radio-card').forEach(card => card.classList.remove('selected'));
firstRadio.classList.add('selected');
const input = firstRadio.querySelector('input[type="radio"]');
if (input) input.checked = true;
}
// Scroll back to form
const formSection = document.getElementById('agentForm');
if (formSection) {
formSection.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
// Initialize Data Analyzer Processor when DOM is ready
document.addEventListener('DOMContentLoaded', function() {
// Initialize processor (data attributes set by template)
window.dataAnalyzerProcessor = new DataAnalyzerProcessor();
// Setup drag and drop functionality
setupDragAndDrop();
});

View File

@ -1,586 +0,0 @@
/**
* Workflows Core - Shared utilities for all agents
* Contains only truly universal functions that ALL agents use identically
*/
class WorkflowsCore {
/**
* Update wallet balance display across the page
*/
static 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);
}
// Update all data attributes
document.querySelectorAll('[data-wallet-balance]').forEach(element => {
element.textContent = `${newBalance.toFixed(2)} AED`;
});
// Update balance in navigation
const headerBalanceNav = document.querySelector('a[href="/wallet/"]');
if (headerBalanceNav) {
headerBalanceNav.textContent = `💰 ${newBalance.toFixed(2)} AED`;
}
// Store current balance globally
window.currentWalletBalance = newBalance;
}
}
/**
* Show toast notification with consistent styling
*/
static showToast(message, type = 'info') {
// Remove existing toasts
document.querySelectorAll('.toast').forEach(toast => toast.remove());
// Create new toast
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.textContent = message;
// Add to page
document.body.appendChild(toast);
// Show toast
setTimeout(() => toast.classList.add('show'), 100);
// Auto remove after 3 seconds
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
/**
* Get CSRF token from page
*/
static getCsrfToken() {
const token = document.querySelector('[name=csrfmiddlewaretoken]');
return token ? token.value : '';
}
/**
* Generate unique session ID for N8N calls
*/
static generateSessionId() {
return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}
/**
* Check user authentication
*/
static checkAuthentication() {
const isAuthenticated = document.body.getAttribute('data-user-authenticated') === 'true';
if (!isAuthenticated) {
this.showToast('Please log in to use this agent', 'error');
setTimeout(() => {
window.location.href = '/auth/login/';
}, 2000);
return false;
}
return true;
}
/**
* Check wallet balance against required amount
*/
static checkBalance(requiredAmount) {
// Get current balance from wallet card
const balanceElement = document.querySelector('[data-wallet-balance]');
if (balanceElement) {
const currentBalance = parseFloat(balanceElement.textContent.replace(/[^\d.]/g, ''));
if (currentBalance < requiredAmount) {
this.showToast(`Insufficient balance. You need ${requiredAmount} AED but have ${currentBalance.toFixed(2)} AED`, 'error');
setTimeout(() => {
window.location.href = '/wallet/';
}, 2000);
return false;
}
}
return true;
}
/**
* Deduct wallet balance via Django API
*/
static async deductBalance(amount, description, agentSlug) {
try {
const response = await fetch('/wallet/api/deduct/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.getCsrfToken()
},
body: JSON.stringify({
amount: amount,
description: description,
agent: agentSlug
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Balance deduction failed');
}
const result = await response.json();
this.updateWalletBalance(result.new_balance);
return result;
} catch (error) {
console.error('Wallet deduction error:', error);
this.showToast(`Payment error: ${error.message}`, 'error');
throw error;
}
}
/**
* Show processing status (common pattern)
*/
static showProcessing(customTitle = 'Processing your request...') {
const processingStatus = document.getElementById('processingStatus');
const resultsContainer = document.getElementById('resultsContainer');
if (processingStatus) {
processingStatus.style.display = 'block';
processingStatus.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
if (resultsContainer) {
resultsContainer.style.display = 'none';
}
// Show processing toast
this.showToast('🔄 ' + customTitle, 'info');
}
/**
* Hide processing status (common pattern)
*/
static hideProcessing() {
const processingStatus = document.getElementById('processingStatus');
if (processingStatus) {
processingStatus.style.display = 'none';
}
}
/**
* Copy text to clipboard with feedback
*/
static async copyToClipboard(text, successMessage = 'Copied to clipboard!') {
try {
await navigator.clipboard.writeText(text);
this.showToast('📋 ' + successMessage, 'success');
} catch (err) {
console.error('Failed to copy:', err);
this.showToast('❌ Failed to copy to clipboard', 'error');
}
}
/**
* Download text as file with feedback
*/
static downloadAsFile(content, filename, successMessage = 'File downloaded!') {
try {
const blob = new Blob([content], { type: 'text/plain' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
// Show success message only if provided
if (successMessage && successMessage.trim()) {
this.showToast('💾 ' + successMessage, 'success');
}
} catch (error) {
console.error('Download error:', error);
this.showToast('❌ Failed to download file', 'error');
}
}
/**
* Format file size for display
*/
static 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];
}
/**
* Show field validation error
*/
static showFieldError(fieldName, message) {
const field = document.getElementById(fieldName);
const errorElement = document.getElementById(`${fieldName}-error`);
if (field) {
field.classList.add('error');
}
if (errorElement) {
errorElement.textContent = message;
errorElement.style.display = 'block';
}
}
/**
* Clear field validation error
*/
static clearFieldError(fieldName) {
const field = document.getElementById(fieldName);
const errorElement = document.getElementById(`${fieldName}-error`);
if (field) {
field.classList.remove('error');
}
if (errorElement) {
errorElement.textContent = '';
errorElement.style.display = 'none';
}
}
/**
* Quick Agent Panel Management (common across all agents)
*/
static toggleQuickAgents() {
const panel = document.getElementById('quickAgentsPanel');
const overlay = document.getElementById('quickAgentsOverlay');
const toggle = document.querySelector('.quick-agent-toggle');
if (!panel || !overlay) return;
const isActive = panel.classList.contains('active');
if (isActive) {
// Close panel
panel.classList.remove('active');
overlay.classList.remove('active');
if (toggle) toggle.classList.remove('active');
// Update ARIA attributes
if (toggle) toggle.setAttribute('aria-expanded', 'false');
panel.setAttribute('aria-hidden', 'true');
overlay.setAttribute('aria-hidden', 'true');
} else {
// Open panel
panel.classList.add('active');
overlay.classList.add('active');
if (toggle) toggle.classList.add('active');
// Update ARIA attributes
if (toggle) toggle.setAttribute('aria-expanded', 'true');
panel.setAttribute('aria-hidden', 'false');
overlay.setAttribute('aria-hidden', 'false');
}
}
static closeQuickAgents() {
const panel = document.getElementById('quickAgentsPanel');
const overlay = document.getElementById('quickAgentsOverlay');
const toggle = document.querySelector('.quick-agent-toggle');
if (panel) panel.classList.remove('active');
if (overlay) overlay.classList.remove('active');
if (toggle) toggle.classList.remove('active');
// Update ARIA attributes
if (toggle) toggle.setAttribute('aria-expanded', 'false');
if (panel) panel.setAttribute('aria-hidden', 'true');
if (overlay) overlay.setAttribute('aria-hidden', 'true');
}
/**
* Initialize form validation on all forms (common pattern)
*/
static initializeFormValidation() {
const forms = document.querySelectorAll('form');
forms.forEach(form => {
const inputs = form.querySelectorAll('input, textarea, select');
inputs.forEach(input => {
input.addEventListener('input', () => {
if (input.value.trim()) {
this.clearFieldError(input.name);
}
});
});
});
}
/**
* Show/hide results container (common pattern)
*/
static showResults(content, title = 'Results') {
const resultsContainer = document.getElementById('resultsContainer');
const resultsTitle = document.querySelector('#resultsContainer .widget-title');
const resultsContent = document.querySelector('#resultsContainer .results-content');
if (resultsContainer) {
resultsContainer.style.display = 'block';
resultsContainer.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
if (resultsTitle) {
resultsTitle.textContent = title;
}
if (resultsContent) {
resultsContent.innerHTML = content;
}
// Hide processing
this.hideProcessing();
}
/**
* Copy agent results to clipboard (common agent function)
*/
static copyResults() {
const content = document.getElementById('resultsContent') || document.querySelector('.results-content');
if (content) {
const text = content.textContent || content.innerText || '';
this.copyToClipboard(text, 'Results copied to clipboard!');
} else {
this.showToast('❌ No results to copy', 'error');
}
}
/**
* Download agent results as file (common agent function)
*/
static downloadResults(filename = 'analysis-results.txt') {
const content = document.getElementById('resultsContent') || document.querySelector('.results-content');
if (content) {
const text = content.textContent || content.innerText || '';
this.downloadAsFile(text, filename, ''); // No toast message
} else {
this.showToast('❌ No results to download', 'error');
}
}
/**
* Reset agent form and hide results (common agent function)
*/
static resetForm() {
const form = document.getElementById('agentForm');
if (form) {
form.reset();
}
const resultsContainer = document.getElementById('resultsContainer');
const processingStatus = document.getElementById('processingStatus');
if (resultsContainer) resultsContainer.style.display = 'none';
if (processingStatus) processingStatus.style.display = 'none';
// Reset file upload areas
const uploadAreas = document.querySelectorAll('.file-upload-area');
uploadAreas.forEach(area => {
area.classList.remove('file-selected');
const uploadText = area.querySelector('.upload-text');
if (uploadText) {
uploadText.innerHTML = `
<div class="upload-icon">📁</div>
<div><strong>Click to upload</strong> or drag and drop</div>
<div>PDF files only</div>
`;
}
});
// Reset radio selections to first option
const radioCards = document.querySelectorAll('.radio-card');
radioCards.forEach(card => card.classList.remove('selected'));
const firstCard = document.querySelector('.radio-card');
if (firstCard) {
firstCard.classList.add('selected');
const input = firstCard.querySelector('input[type="radio"]');
if (input) input.checked = true;
}
// Form reset complete - no toast needed (visual feedback is sufficient)
}
/**
* Setup drag and drop for file inputs (enhanced from prototype)
*/
static setupDragAndDrop(container, fileInput) {
if (!container || !fileInput) return;
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
container.addEventListener(eventName, preventDefaults, false);
});
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
container.addEventListener(eventName, () => {
container.classList.add('dragover');
}, false);
});
['dragleave', 'drop'].forEach(eventName => {
container.addEventListener(eventName, () => {
container.classList.remove('dragover');
}, false);
});
container.addEventListener('drop', (e) => {
const files = e.dataTransfer.files;
if (files.length > 0) {
fileInput.files = files;
fileInput.dispatchEvent(new Event('change'));
}
}, false);
}
/**
* Handle file input change (common pattern for file uploads)
*/
static handleFileChange(fileInput) {
const file = fileInput.files[0];
const fieldName = fileInput.name;
const container = fileInput.closest('.file-upload-container');
if (!file || !container) return;
const fileInfo = container.querySelector(`#${fieldName}_file_info`);
const fileName = fileInfo?.querySelector('.file-name');
const fileSize = fileInfo?.querySelector('.file-size');
const uploadArea = container.querySelector(`#${fieldName}_upload_area`);
if (fileName) fileName.textContent = file.name;
if (fileSize) fileSize.textContent = this.formatFileSize(file.size);
if (fileInfo) fileInfo.style.display = 'block';
if (uploadArea) uploadArea.style.display = 'none';
// Clear any previous errors
this.clearFieldError(fieldName);
// Show success feedback
this.showToast(`📁 File selected: ${file.name}`, 'success');
}
/**
* Remove selected file (common pattern)
*/
static removeFile(fieldName) {
const fileInput = document.getElementById(fieldName);
const container = fileInput?.closest('.file-upload-container');
if (!fileInput || !container) return;
const fileInfo = container.querySelector(`#${fieldName}_file_info`);
const uploadArea = container.querySelector(`#${fieldName}_upload_area`);
// Clear file input
fileInput.value = '';
// Hide file info, show upload area
if (fileInfo) fileInfo.style.display = 'none';
if (uploadArea) uploadArea.style.display = 'block';
this.showToast('📁 File removed', 'info');
}
}
// Global utility functions that all agents can use
function toggleQuickAgents() {
WorkflowsCore.toggleQuickAgents();
}
function closeQuickAgents() {
WorkflowsCore.closeQuickAgents();
}
// Global convenience functions for common actions
function removeFile(fieldName) {
WorkflowsCore.removeFile(fieldName);
}
function showToast(message, type = 'info') {
WorkflowsCore.showToast(message, type);
}
function copyToClipboard(text, successMessage) {
WorkflowsCore.copyToClipboard(text, successMessage);
}
function downloadAsFile(content, filename, successMessage) {
WorkflowsCore.downloadAsFile(content, filename, successMessage);
}
// Global convenience functions for common agent actions
function copyResults() {
WorkflowsCore.copyResults();
}
function downloadResults(filename) {
WorkflowsCore.downloadResults(filename);
}
function resetForm() {
WorkflowsCore.resetForm();
}
// Initialize on DOM load
document.addEventListener('DOMContentLoaded', function() {
// Initialize form validation for all forms
WorkflowsCore.initializeFormValidation();
// Setup file upload drag and drop for any file inputs
const fileInputs = document.querySelectorAll('input[type="file"]');
fileInputs.forEach(input => {
const container = input.closest('.file-upload-container');
if (container) {
WorkflowsCore.setupDragAndDrop(container, input);
}
// Setup file change handler
input.addEventListener('change', () => {
WorkflowsCore.handleFileChange(input);
});
});
// Set initial ARIA states for quick agents panel
const quickAgentsButton = document.querySelector('.quick-agent-toggle');
const panel = document.getElementById('quickAgentsPanel');
const overlay = document.getElementById('quickAgentsOverlay');
if (quickAgentsButton) quickAgentsButton.setAttribute('aria-expanded', 'false');
if (panel) panel.setAttribute('aria-hidden', 'true');
if (overlay) overlay.setAttribute('aria-hidden', 'true');
});
// Close panel with Escape key (common functionality)
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
WorkflowsCore.closeQuickAgents();
}
});
// Export for module usage if needed
if (typeof module !== 'undefined' && module.exports) {
module.exports = WorkflowsCore;
}

View File

@ -1,684 +0,0 @@
/**
* Universal Workflows JavaScript Framework
* Handles all agent interactions with direct N8N integration
*/
class WorkflowProcessor {
constructor(agentSlug, webhookUrl, price) {
this.agentSlug = agentSlug;
this.webhookUrl = webhookUrl;
this.price = price;
this.sessionId = this.generateSessionId();
this.processing = false;
}
/**
* Handle form submission - main entry point
*/
async handleFormSubmission(event) {
event.preventDefault();
if (this.processing) {
this.showToast('Please wait, processing your previous request...', 'warning');
return;
}
const form = event.target;
const formData = new FormData(form);
// Convert FormData to object
const data = {};
for (let [key, value] of formData.entries()) {
data[key] = value;
}
await this.processWorkflow(data, formData);
}
/**
* Main workflow processing function
*/
async processWorkflow(data, formData = null) {
try {
this.processing = true;
// 1. Validate form
if (!this.validateForm(data)) {
this.processing = false;
return;
}
// 2. Check authentication and balance
if (!await this.checkBalance()) {
this.processing = false;
return;
}
// 3. Show processing status
this.showProcessing();
// 4. Call N8N directly
const result = await this.callN8N(data, formData);
if (result && result.output) {
// 5. Deduct balance via Django API
await this.deductBalance();
// 6. Display results
this.displayResults(result);
this.showToast('Processing completed successfully!', 'success');
} else {
throw new Error('No output received from N8N');
}
} catch (error) {
console.error('Workflow processing error:', error);
this.showError(`Processing failed: ${error.message}`);
this.showToast('Processing failed. Please try again.', 'error');
} finally {
this.processing = false;
this.hideProcessing();
}
}
/**
* Call N8N webhook directly
*/
async callN8N(data, formData = null) {
const messageText = this.formatMessage(data);
const payload = {
sessionId: this.sessionId,
message: { text: messageText },
agentSlug: this.agentSlug,
timestamp: new Date().toISOString()
};
// Handle file uploads if present
if (formData && this.hasFileUploads(data)) {
// For file uploads, we need to handle differently
return await this.callN8NWithFiles(messageText, formData);
}
const response = await fetch(this.webhookUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload)
});
if (!response.ok) {
throw new Error(`N8N webhook failed: ${response.status} ${response.statusText}`);
}
return await response.json();
}
/**
* Handle N8N calls with file uploads
*/
async callN8NWithFiles(messageText, formData) {
// Create multipart form data for file uploads
const uploadData = new FormData();
uploadData.append('sessionId', this.sessionId);
uploadData.append('message', JSON.stringify({ text: messageText }));
uploadData.append('agentSlug', this.agentSlug);
// Add files
for (let [key, value] of formData.entries()) {
if (value instanceof File) {
uploadData.append(key, value);
}
}
const response = await fetch(this.webhookUrl, {
method: 'POST',
body: uploadData
});
if (!response.ok) {
throw new Error(`N8N webhook with files failed: ${response.status} ${response.statusText}`);
}
return await response.json();
}
/**
* Deduct wallet balance via Django API
*/
async deductBalance() {
const response = await fetch('/wallet/api/deduct/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.getCsrfToken()
},
body: JSON.stringify({
amount: this.price,
description: `${this.agentSlug} processing`,
agent: this.agentSlug
})
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Balance deduction failed');
}
const result = await response.json();
this.updateWalletBalance(result.new_balance);
return result;
}
/**
* Validate form data
*/
validateForm(data) {
const requiredFields = document.querySelectorAll('[required]');
let isValid = true;
requiredFields.forEach(field => {
const value = data[field.name];
if (!value || (typeof value === 'string' && value.trim() === '')) {
this.showFieldError(field, 'This field is required');
isValid = false;
} else {
this.clearFieldError(field);
}
});
return isValid;
}
/**
* Check user authentication and balance
*/
async checkBalance() {
const isAuthenticated = document.body.getAttribute('data-user-authenticated') === 'true';
if (!isAuthenticated) {
this.showToast('Please log in to use this agent', 'error');
setTimeout(() => {
window.location.href = '/auth/login/';
}, 2000);
return false;
}
// Get current balance from wallet card
const balanceElement = document.querySelector('[data-wallet-balance]');
if (balanceElement) {
const currentBalance = parseFloat(balanceElement.textContent.replace(/[^\d.]/g, ''));
if (currentBalance < this.price) {
this.showToast(`Insufficient balance. You need ${this.price} AED but have ${currentBalance} AED`, 'error');
return false;
}
}
return true;
}
/**
* Format message for N8N based on agent configuration
*/
formatMessage(data) {
// Create a descriptive message based on the agent and data
let message = `Process ${this.agentSlug} request:\n\n`;
for (const [key, value] of Object.entries(data)) {
if (value && key !== 'csrfmiddlewaretoken') {
const fieldLabel = this.getFieldLabel(key) || key.replace(/[_-]/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
message += `${fieldLabel}: ${value}\n`;
}
}
return message.trim();
}
/**
* Get field label from DOM
*/
getFieldLabel(fieldName) {
const field = document.querySelector(`[name="${fieldName}"]`);
if (field) {
const label = document.querySelector(`label[for="${field.id}"]`);
if (label) {
return label.textContent.replace('*', '').trim();
}
}
return null;
}
/**
* Check if form has file uploads
*/
hasFileUploads(data) {
return Object.values(data).some(value => value instanceof File);
}
/**
* Display processing status
*/
showProcessing() {
const processingStatus = document.getElementById('processingStatus');
const resultsContainer = document.getElementById('resultsContainer');
const submitBtn = document.getElementById('submitBtn');
if (processingStatus) {
processingStatus.style.display = 'block';
processingStatus.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
if (resultsContainer) {
resultsContainer.style.display = 'none';
}
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.textContent = 'Processing...';
}
}
/**
* Hide processing status
*/
hideProcessing() {
const processingStatus = document.getElementById('processingStatus');
const submitBtn = document.getElementById('submitBtn');
if (processingStatus) {
processingStatus.style.display = 'none';
}
if (submitBtn) {
submitBtn.disabled = false;
submitBtn.textContent = `🚀 Process with ${this.agentSlug.replace(/-/g, ' ')} (${this.price} AED)`;
}
}
/**
* Display results
*/
displayResults(result) {
const resultsContainer = document.getElementById('resultsContainer');
const resultsContent = document.querySelector('.results-content');
if (!resultsContainer || !resultsContent) return;
// Clear previous results
resultsContent.innerHTML = '';
// Create result content
const resultDiv = document.createElement('div');
resultDiv.className = 'workflow-result';
if (result.output) {
// Create formatted output
const outputDiv = document.createElement('div');
outputDiv.className = 'result-output';
// Handle different output formats
if (typeof result.output === 'string') {
outputDiv.innerHTML = this.formatTextOutput(result.output);
} else if (typeof result.output === 'object') {
outputDiv.innerHTML = this.formatObjectOutput(result.output);
} else {
outputDiv.textContent = String(result.output);
}
resultDiv.appendChild(outputDiv);
}
// Add action buttons
const actionsDiv = document.createElement('div');
actionsDiv.className = 'result-actions';
actionsDiv.innerHTML = `
<button class="btn btn-secondary" onclick="workflowProcessor.copyResults()">
📋 Copy Results
</button>
<button class="btn btn-secondary" onclick="workflowProcessor.downloadResults()">
💾 Download
</button>
<button class="btn btn-primary" onclick="workflowProcessor.newRequest()">
🔄 New Request
</button>
`;
resultDiv.appendChild(actionsDiv);
resultsContent.appendChild(resultDiv);
// Show results container
resultsContainer.style.display = 'block';
resultsContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
// Store results for actions
this.lastResult = result;
}
/**
* Format text output with proper styling
*/
formatTextOutput(text) {
// Convert newlines to HTML breaks and preserve formatting
return text
.replace(/\n\n/g, '</p><p>')
.replace(/\n/g, '<br>')
.replace(/^(.*)/, '<p>$1')
.replace(/(.*?)$/, '$1</p>')
.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>') // Bold
.replace(/\*(.*?)\*/g, '<em>$1</em>'); // Italic
}
/**
* Format object output as structured data
*/
formatObjectOutput(obj) {
if (obj.formatted_content) {
return this.formatTextOutput(obj.formatted_content);
}
let html = '<div class="structured-output">';
for (const [key, value] of Object.entries(obj)) {
if (value && key !== 'raw_data') {
const label = key.replace(/[_-]/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
html += `<div class="output-item">`;
html += `<strong>${label}:</strong> `;
if (typeof value === 'string') {
html += this.formatTextOutput(value);
} else {
html += String(value);
}
html += `</div>`;
}
}
html += '</div>';
return html;
}
/**
* Copy results to clipboard
*/
async copyResults() {
if (!this.lastResult) return;
let textToCopy = '';
if (typeof this.lastResult.output === 'string') {
textToCopy = this.lastResult.output;
} else if (typeof this.lastResult.output === 'object') {
textToCopy = JSON.stringify(this.lastResult.output, null, 2);
}
try {
await navigator.clipboard.writeText(textToCopy);
this.showToast('Results copied to clipboard!', 'success');
} catch (err) {
this.showToast('Failed to copy to clipboard', 'error');
}
}
/**
* Download results as text file
*/
downloadResults() {
if (!this.lastResult) return;
let content = '';
if (typeof this.lastResult.output === 'string') {
content = this.lastResult.output;
} else {
content = JSON.stringify(this.lastResult.output, null, 2);
}
const blob = new Blob([content], { type: 'text/plain' });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${this.agentSlug}-result-${new Date().toISOString().slice(0, 10)}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
this.showToast('Results downloaded!', 'success');
}
/**
* Reset form for new request
*/
newRequest() {
const form = document.getElementById('workflowForm');
if (form) {
form.reset();
// Clear file uploads
document.querySelectorAll('.file-info').forEach(info => {
info.style.display = 'none';
});
// Reset radio cards
document.querySelectorAll('.radio-card').forEach(card => {
card.classList.remove('selected');
});
}
// Hide results
const resultsContainer = document.getElementById('resultsContainer');
if (resultsContainer) {
resultsContainer.style.display = 'none';
}
// Scroll to form
form.scrollIntoView({ behavior: 'smooth', block: 'start' });
this.showToast('Ready for new request', 'info');
}
/**
* Show error message
*/
showError(message) {
const resultsContainer = document.getElementById('resultsContainer');
const resultsContent = document.querySelector('.results-content');
if (resultsContainer && resultsContent) {
resultsContent.innerHTML = `
<div class="error-message">
<div class="error-icon"></div>
<div class="error-text">${message}</div>
<button class="btn btn-primary" onclick="workflowProcessor.newRequest()">
🔄 Try Again
</button>
</div>
`;
resultsContainer.style.display = 'block';
resultsContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
/**
* Show field error
*/
showFieldError(field, message) {
this.clearFieldError(field);
const errorDiv = document.createElement('div');
errorDiv.className = 'field-error';
errorDiv.textContent = message;
errorDiv.id = `${field.name}_error`;
field.parentNode.appendChild(errorDiv);
field.classList.add('error');
}
/**
* Clear field error
*/
clearFieldError(field) {
const existingError = document.getElementById(`${field.name}_error`);
if (existingError) {
existingError.remove();
}
field.classList.remove('error');
}
/**
* Update wallet balance display
*/
updateWalletBalance(newBalance) {
if (newBalance !== undefined) {
// Update all balance displays
document.querySelectorAll('[data-wallet-balance]').forEach(element => {
element.textContent = `${newBalance.toFixed(2)} AED`;
});
// Update balance in navigation
const headerBalance = document.querySelector('a[href="/wallet/"]');
if (headerBalance) {
headerBalance.textContent = `💰 ${newBalance.toFixed(2)} AED`;
}
// Store current balance globally
window.currentWalletBalance = newBalance;
}
}
/**
* Show toast notification
*/
showToast(message, type = 'info') {
// Remove existing toasts
document.querySelectorAll('.toast').forEach(toast => toast.remove());
// Create new toast
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.textContent = message;
// Add to page
document.body.appendChild(toast);
// Show toast
setTimeout(() => toast.classList.add('show'), 100);
// Auto remove after 3 seconds
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
/**
* Handle file input changes
*/
handleFileChange(event) {
const input = event.target;
const file = input.files[0];
const container = input.closest('.file-upload-container');
if (!container) return;
const fileInfo = container.querySelector('.file-info');
const uploadArea = container.querySelector('.file-upload-area');
if (file && fileInfo) {
// Show file info
const fileName = fileInfo.querySelector('.file-name');
const fileSize = fileInfo.querySelector('.file-size');
if (fileName) fileName.textContent = file.name;
if (fileSize) fileSize.textContent = this.formatFileSize(file.size);
fileInfo.style.display = 'block';
uploadArea.classList.add('has-file');
}
}
/**
* Setup drag and drop for file uploads
*/
setupDragAndDrop(container, input) {
const uploadArea = container.querySelector('.file-upload-area');
if (!uploadArea) return;
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('drag-over');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('drag-over');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('drag-over');
const files = e.dataTransfer.files;
if (files.length > 0) {
input.files = files;
this.handleFileChange({ target: input });
}
});
uploadArea.addEventListener('click', () => {
input.click();
});
}
/**
* Format file size for display
*/
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];
}
/**
* Generate unique session ID
*/
generateSessionId() {
return 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
}
/**
* Get CSRF token from page
*/
getCsrfToken() {
const token = document.querySelector('[name=csrfmiddlewaretoken]');
return token ? token.value : '';
}
}
// Global utility functions
function removeFile(fieldName) {
const input = document.getElementById(fieldName);
const container = input.closest('.file-upload-container');
if (input) input.value = '';
if (container) {
const fileInfo = container.querySelector('.file-info');
const uploadArea = container.querySelector('.file-upload-area');
if (fileInfo) fileInfo.style.display = 'none';
if (uploadArea) uploadArea.classList.remove('has-file');
}
}
function selectRadio(name, value) {
// Remove selection from all radio cards with this name
document.querySelectorAll(`input[name="${name}"]`).forEach(radio => {
radio.closest('.radio-card').classList.remove('selected');
radio.checked = false;
});
// Select the clicked radio
const radio = document.querySelector(`input[name="${name}"][value="${value}"]`);
if (radio) {
radio.checked = true;
radio.closest('.radio-card').classList.add('selected');
}
}

View File

@ -1,95 +0,0 @@
#!/usr/bin/env python
"""
Final test of the updated social ads processor
"""
import requests
import json
from datetime import datetime
def test_final_webhook():
"""Test the final corrected webhook format"""
webhook_url = "http://localhost:5678/webhook/2dc234d8-7217-454a-83e9-81afe5b4fe2d"
# Simulate the data that would come from the Django form
form_data = {
'user_id': 1,
'description': 'Revolutionary AI-powered fitness tracker that monitors your health 24/7',
'social_platform': 'instagram',
'include_emoji': True,
'language': 'English',
'cost': 7.00
}
# Format as the processor now does
emoji_text = "Yes" if form_data['include_emoji'] else "No"
platform_display = form_data['social_platform'].title()
message_text = f"""Create a social media advertisement with the following details:
Description: {form_data['description']}
Include Emoji: {emoji_text}
Social Media Platform: {platform_display}
Language: {form_data['language']}
Please create an engaging, platform-optimized social media ad based on this information."""
payload = {
'message': {
'text': message_text
},
'sessionId': f'social_ad_{form_data["user_id"]}_{int(datetime.now().timestamp() * 1000)}'
}
print("🎯 Final Webhook Test - Social Ads Generator")
print("=" * 60)
print(f"Webhook URL: {webhook_url}")
print(f"\nForm Data: {json.dumps(form_data, indent=2)}")
print(f"\nFormatted Payload:")
print(json.dumps(payload, indent=2))
print("-" * 60)
try:
response = requests.post(webhook_url, json=payload, timeout=30)
print(f"✅ Status Code: {response.status_code}")
print(f"✅ Response Headers: {dict(response.headers)}")
if response.status_code == 200:
try:
json_response = response.json()
ad_content = json_response.get('output', '')
print(f"\n🎉 SUCCESS! Generated Ad Content:")
print("-" * 40)
print(ad_content)
print("-" * 40)
print(f"\n📊 Response Analysis:")
print(f"- Content Length: {len(ad_content)} characters")
print(f"- Contains Emojis: {'Yes' if any(ord(char) > 127 for char in ad_content) else 'No'}")
print(f"- Platform Optimized: Instagram format detected")
return True
except json.JSONDecodeError:
print(f"❌ Invalid JSON response: {response.text}")
return False
else:
print(f"❌ HTTP Error {response.status_code}: {response.text}")
return False
except Exception as e:
print(f"❌ Request failed: {e}")
return False
if __name__ == "__main__":
success = test_final_webhook()
print("\n" + "=" * 60)
if success:
print("🎉 WEBHOOK TEST PASSED! The social ads generator is working correctly.")
print("✅ The processor format has been updated to match N8N expectations.")
print("✅ Ready for production use!")
else:
print("❌ WEBHOOK TEST FAILED! Check the error messages above.")
print("=" * 60)

View File

@ -1,153 +0,0 @@
#!/usr/bin/env python
"""
Final test of the updated Five Whys processor
"""
import requests
import json
from datetime import datetime
def test_final_five_whys():
"""Test the final corrected Five Whys webhook format"""
webhook_url = "https://quantumtaskai.app.n8n.cloud/webhook/5-whys-web"
# Simulate the data that would come from the Django form
form_data = {
'user_id': 'test-user-456',
'problem_statement': 'Our e-commerce website has a high cart abandonment rate',
'problem_category': 'customer',
'context_information': 'Cart abandonment rate is 75%, industry average is 50%. Customers add items but leave before checkout.',
'include_solutions': True,
'cost': 8.00
}
# Format as the processor now does
message_text = f"""Perform a Five Whys root cause analysis with the following details:
Problem Statement: {form_data['problem_statement']}
Problem Category: {form_data['problem_category']}
Context Information: {form_data['context_information']}
Include Solutions: {'Yes' if form_data['include_solutions'] else 'No'}
Please conduct a systematic Five Whys analysis to identify the root cause and provide actionable solutions."""
payload = {
'message': {
'text': message_text
},
'sessionId': f'five_whys_{int(datetime.now().timestamp() * 1000)}',
'userId': form_data['user_id'],
'agentId': '5',
'problemStatement': form_data['problem_statement'],
'problemCategory': form_data['problem_category']
}
print("🔍 Final Five Whys Webhook Test")
print("=" * 60)
print(f"Webhook URL: {webhook_url}")
print(f"\nForm Data: {json.dumps(form_data, indent=2)}")
print(f"\nFormatted Payload:")
print(json.dumps(payload, indent=2))
print("-" * 60)
try:
response = requests.post(webhook_url, json=payload, timeout=60)
print(f"✅ Status Code: {response.status_code}")
print(f"✅ Response Headers: {dict(response.headers)}")
if response.status_code == 200:
try:
json_response = response.json()
analysis_content = json_response.get('output', '')
print(f"\n🎉 SUCCESS! Generated Five Whys Analysis:")
print("-" * 40)
print(analysis_content)
print("-" * 40)
print(f"\n📊 Response Analysis:")
print(f"- Content Length: {len(analysis_content)} characters")
print(f"- Contains 'Why': {'Yes' if 'Why' in analysis_content else 'No'}")
print(f"- Contains 'Root Cause': {'Yes' if 'root cause' in analysis_content.lower() else 'No'}")
print(f"- Contains 'Solution': {'Yes' if 'solution' in analysis_content.lower() else 'No'}")
print(f"- Contains Problem Statement: {'Yes' if form_data['problem_statement'] in analysis_content else 'No'}")
return True
except json.JSONDecodeError:
print(f"❌ Invalid JSON response: {response.text}")
return False
else:
print(f"❌ HTTP Error {response.status_code}: {response.text}")
return False
except Exception as e:
print(f"❌ Request failed: {e}")
return False
def test_different_problem():
"""Test with a different type of problem"""
webhook_url = "https://quantumtaskai.app.n8n.cloud/webhook/5-whys-web"
# Test with a technical problem
message_text = """Perform a Five Whys root cause analysis with the following details:
Problem Statement: Server downtime incidents are increasing
Problem Category: technical
Context Information: 3 incidents this month, each lasting 2+ hours. Users unable to access application.
Include Solutions: Yes
Please conduct a systematic Five Whys analysis to identify the root cause and provide actionable solutions."""
payload = {
'message': {
'text': message_text
},
'sessionId': f'five_whys_{int(datetime.now().timestamp() * 1000)}',
'userId': 'test-user-789',
'agentId': '5',
'problemStatement': 'Server downtime incidents are increasing',
'problemCategory': 'technical'
}
print("\n" + "=" * 60)
print("🔍 Testing Different Problem Type - Technical")
print("=" * 60)
try:
response = requests.post(webhook_url, json=payload, timeout=60)
if response.status_code == 200:
json_response = response.json()
analysis_content = json_response.get('output', '')
print(f"✅ Technical problem analysis generated ({len(analysis_content)} chars)")
return True
else:
print(f"❌ Failed with status {response.status_code}")
return False
except Exception as e:
print(f"❌ Request failed: {e}")
return False
if __name__ == "__main__":
print("🚀 Testing Updated Five Whys Webhook")
print("=" * 60)
# Test main scenario
success1 = test_final_five_whys()
# Test different problem type
success2 = test_different_problem()
print("\n" + "=" * 60)
if success1 and success2:
print("🎉 FIVE WHYS WEBHOOK TESTS PASSED!")
print("✅ The processor format has been updated to match N8N expectations.")
print("✅ Works with different problem types and categories.")
print("✅ Ready for production use!")
else:
print("❌ FIVE WHYS WEBHOOK TESTS FAILED!")
print("Check the error messages above.")
print("=" * 60)

View File

@ -1,144 +0,0 @@
#!/usr/bin/env python
"""
Test the Five Whys webhook to understand expected format
"""
import requests
import json
from datetime import datetime
def test_current_format():
"""Test with our current format"""
webhook_url = "https://quantumtaskai.app.n8n.cloud/webhook/5-whys-web"
# Current format we're sending
current_payload = {
'user_id': 'test-user-123',
'problem_statement': 'Our customer support response time is too slow',
'problem_category': 'operational',
'context_information': 'Average response time is 4 hours, customers complaining',
'include_solutions': True,
'agent_type': 'five_whys_analyzer',
'cost': 8.00,
'timestamp': datetime.now().isoformat() + 'Z'
}
print("🔍 Testing Five Whys Webhook - Current Format")
print("=" * 60)
print(f"Webhook URL: {webhook_url}")
print(f"Current Payload: {json.dumps(current_payload, indent=2)}")
print("-" * 60)
try:
response = requests.post(webhook_url, json=current_payload, timeout=30)
print(f"Status Code: {response.status_code}")
print(f"Response: {response.text[:500]}...")
if response.status_code == 200:
print("✅ Current format working!")
return True
else:
print("❌ Current format not working")
return False
except Exception as e:
print(f"❌ Request failed: {e}")
return False
def test_message_format():
"""Test with message format like other agents"""
webhook_url = "https://quantumtaskai.app.n8n.cloud/webhook/5-whys-web"
# Format similar to social ads and job posting
problem_statement = "Our customer support response time is too slow"
problem_category = "operational"
context_information = "Average response time is 4 hours, customers complaining"
include_solutions = True
message_text = f"""Perform a Five Whys root cause analysis with the following details:
Problem Statement: {problem_statement}
Problem Category: {problem_category}
Context Information: {context_information}
Include Solutions: {'Yes' if include_solutions else 'No'}
Please conduct a systematic Five Whys analysis to identify the root cause and provide actionable solutions."""
message_payload = {
'message': {
'text': message_text
},
'sessionId': f'five_whys_{int(datetime.now().timestamp() * 1000)}',
'userId': 'test-user-123',
'agentId': '5', # Five Whys agent ID
'problemStatement': problem_statement,
'problemCategory': problem_category
}
print("\n" + "=" * 60)
print("🔍 Testing Five Whys Webhook - Message Format")
print("=" * 60)
print(f"Message Payload: {json.dumps(message_payload, indent=2)}")
print("-" * 60)
try:
response = requests.post(webhook_url, json=message_payload, timeout=30)
print(f"Status Code: {response.status_code}")
print(f"Response: {response.text[:500]}...")
if response.status_code == 200:
print("✅ Message format working!")
return True
else:
print("❌ Message format not working")
return False
except Exception as e:
print(f"❌ Request failed: {e}")
return False
def test_simple_connectivity():
"""Test basic connectivity"""
webhook_url = "https://quantumtaskai.app.n8n.cloud/webhook/5-whys-web"
print("🔗 Testing Basic Connectivity")
print("-" * 30)
try:
response = requests.post(webhook_url, json={'test': 'ping'}, timeout=10)
print(f"✅ Connectivity: OK (Status: {response.status_code})")
print(f"Response: {response.text[:200]}...")
return True
except Exception as e:
print(f"❌ Connectivity: FAILED ({e})")
return False
if __name__ == "__main__":
print("🚀 Testing Five Whys Webhook Formats")
print("=" * 60)
# Test basic connectivity
if test_simple_connectivity():
print("\n" + "=" * 60)
# Test current format
current_works = test_current_format()
# Test message format
message_works = test_message_format()
print("\n" + "=" * 60)
print("📊 RESULTS SUMMARY:")
print(f"Current Format: {'✅ WORKS' if current_works else '❌ FAILED'}")
print(f"Message Format: {'✅ WORKS' if message_works else '❌ FAILED'}")
if current_works:
print("\n💡 Current format is working - no changes needed!")
elif message_works:
print("\n💡 Need to update to message format!")
else:
print("\n⚠️ Both formats failed - need to investigate webhook structure")
else:
print("\n❌ Cannot test formats - webhook not reachable")
print("=" * 60)

View File

@ -1,136 +0,0 @@
#!/usr/bin/env python
"""
Test the job posting webhook with updated format
"""
import requests
import json
from datetime import datetime
def test_job_posting_webhook():
"""Test the job posting webhook connectivity"""
webhook_url = "http://localhost:5678/webhook/43f84411-eaaa-488c-9b1f-856e90d0aaf6"
# Simulate the data that would come from the Django form
form_data = {
'user_id': 'test-user-123',
'job_title': 'Senior Software Developer',
'company_name': 'Quantum Tasks AI Technologies',
'industry': 'technology',
'job_type': 'full-time',
'experience_level': 'senior',
'location': 'Remote',
'salary_range': '$80,000 - $120,000',
'key_responsibilities': 'Develop and maintain web applications, lead technical projects, mentor junior developers',
'required_skills': 'Python, Django, React, PostgreSQL, AWS, Git',
'company_culture': 'Innovative, collaborative, work-life balance focused',
'cost': 10.00
}
# Format as the processor now does
message_text = f"""Create a professional job posting with the following details:
Job Title: {form_data['job_title']}
Company: {form_data['company_name']}
Description: {form_data['key_responsibilities']}
Seniority Level: {form_data['experience_level']}
Contract Type: {form_data['job_type']}
Location: {form_data['location']}
Language: English
Required Skills: {form_data['required_skills']}
Salary Range: {form_data['salary_range']}
Company Culture: {form_data['company_culture']}
Please create a complete, engaging job posting based on this information."""
payload = {
'message': {
'text': message_text
},
'sessionId': f'job_posting_{int(datetime.now().timestamp() * 1000)}',
'userId': form_data['user_id'],
'agentId': '9',
'jobTitle': form_data['job_title'],
'companyName': form_data['company_name']
}
print("💼 Job Posting Webhook Test")
print("=" * 60)
print(f"Webhook URL: {webhook_url}")
print(f"\nForm Data: {json.dumps(form_data, indent=2)}")
print(f"\nFormatted Payload:")
print(json.dumps(payload, indent=2))
print("-" * 60)
try:
response = requests.post(webhook_url, json=payload, timeout=60)
print(f"✅ Status Code: {response.status_code}")
print(f"✅ Response Headers: {dict(response.headers)}")
if response.status_code == 200:
try:
json_response = response.json()
job_content = json_response.get('output', '')
print(f"\n🎉 SUCCESS! Generated Job Posting:")
print("-" * 40)
print(job_content)
print("-" * 40)
print(f"\n📊 Response Analysis:")
print(f"- Content Length: {len(job_content)} characters")
print(f"- Contains Company Name: {'Yes' if form_data['company_name'] in job_content else 'No'}")
print(f"- Contains Job Title: {'Yes' if form_data['job_title'] in job_content else 'No'}")
print(f"- Professional Format: Job posting format detected")
return True
except json.JSONDecodeError:
print(f"❌ Invalid JSON response: {response.text}")
return False
else:
print(f"❌ HTTP Error {response.status_code}: {response.text}")
return False
except Exception as e:
print(f"❌ Request failed: {e}")
return False
def test_simple_connectivity():
"""Simple test to check if webhook endpoint exists"""
webhook_url = "http://localhost:5678/webhook/43f84411-eaaa-488c-9b1f-856e90d0aaf6"
print("🔗 Simple Connectivity Test")
print("-" * 30)
try:
# Simple POST with minimal data to test connectivity
response = requests.post(webhook_url, json={'test': 'ping'}, timeout=10)
print(f"✅ Connectivity: OK (Status: {response.status_code})")
return True
except Exception as e:
print(f"❌ Connectivity: FAILED ({e})")
return False
if __name__ == "__main__":
print("🚀 Testing Job Posting Webhook")
print("=" * 60)
# Test connectivity first
if test_simple_connectivity():
print("\n" + "=" * 60)
success = test_job_posting_webhook()
print("\n" + "=" * 60)
if success:
print("🎉 JOB POSTING WEBHOOK TEST PASSED!")
print("✅ The processor format has been updated to match N8N expectations.")
print("✅ Ready for production use!")
else:
print("❌ JOB POSTING WEBHOOK TEST FAILED!")
print("Check the error messages above.")
else:
print("\n❌ Cannot proceed with full test - webhook endpoint not reachable")
print("=" * 60)

View File

@ -1,78 +0,0 @@
#!/usr/bin/env python
import os
import sys
import django
# Add the project root to Python path
sys.path.insert(0, '/home/amit/projects/quantumtaskai_django')
# Set Django settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'quantumtaskai_hub.settings')
django.setup()
from django.contrib.auth import get_user_model
from agent_base.models import BaseAgent
from weather_reporter.models import WeatherReporterRequest, WeatherReporterResponse
from weather_reporter.processor import WeatherReporterProcessor
User = get_user_model()
def test_weather_agent():
print("🧪 Testing Weather Reporter Agent System")
print("=" * 50)
# Get or create a test user
user, created = User.objects.get_or_create(
email='test@example.com',
defaults={
'username': 'testuser',
'wallet_balance': 100.00 # Give them some balance
}
)
if created:
print(f"✅ Created test user: {user.email} with balance: {user.wallet_balance} AED")
else:
print(f"✅ Using existing test user: {user.email} with balance: {user.wallet_balance} AED")
# Get the weather agent
try:
agent = BaseAgent.objects.get(slug='weather-reporter')
print(f"✅ Found Weather Reporter agent: {agent.name} (Price: {agent.price} AED)")
except BaseAgent.DoesNotExist:
print("❌ Weather Reporter agent not found in database")
return False
# Test the processor directly (without API key for now)
processor = WeatherReporterProcessor()
print(f"✅ Created Weather Reporter processor: {processor.agent_slug}")
# Create a test request
request_obj = WeatherReporterRequest.objects.create(
user=user,
agent=agent,
cost=agent.price,
location='London',
report_type='current'
)
print(f"✅ Created test request: {request_obj.id}")
# Test webhook format detection
from agent_base.processors import WebhookFormatDetector
print("\n🔍 Testing Webhook Format Detector:")
print("This tests the webhook format detection utility...")
# Note: We won't test with real URLs to avoid network calls
print("✅ WebhookFormatDetector class loaded successfully")
print("\n🎉 Weather Reporter Agent System Test Complete!")
print("=" * 50)
print("✅ Agent Base Framework: Working")
print("✅ Weather Reporter Agent: Created")
print("✅ Models & Database: Working")
print("✅ Processor Classes: Working")
print("✅ Management Commands: Working")
print("✅ Template System: Working")
return True
if __name__ == '__main__':
test_weather_agent()

View File

@ -1,115 +0,0 @@
#!/usr/bin/env python
"""
Simple webhook connectivity test for social ads generator
"""
import requests
import json
from datetime import datetime
def test_social_ads_webhook():
"""Test the social ads webhook connectivity"""
webhook_url = "http://localhost:5678/webhook/2dc234d8-7217-454a-83e9-81afe5b4fe2d"
# Test payload using the correct format expected by N8N
message_text = """Create a social media advertisement with the following details:
Description: Amazing smartphone with cutting-edge features
Include Emoji: Yes
Social Media Platform: Facebook
Language: English
Please create an engaging, platform-optimized social media ad based on this information."""
test_payload = {
'message': {
'text': message_text
},
'sessionId': f'social_ad_test_{int(datetime.now().timestamp() * 1000)}'
}
print(f"Testing webhook: {webhook_url}")
print(f"Payload: {json.dumps(test_payload, indent=2)}")
print("-" * 50)
try:
# Send POST request to webhook
response = requests.post(
webhook_url,
json=test_payload,
timeout=30,
headers={'Content-Type': 'application/json'}
)
print(f"✅ Response Status: {response.status_code}")
print(f"✅ Response Headers: {dict(response.headers)}")
print(f"✅ Response Content: {response.text}")
if response.status_code == 200:
print("\n🎉 SUCCESS: Webhook is reachable and responding!")
# Try to parse JSON response
try:
json_response = response.json()
print(f"📄 JSON Response: {json.dumps(json_response, indent=2)}")
except:
print("📄 Response is not JSON format")
else:
print(f"\n⚠️ WARNING: Webhook returned status code {response.status_code}")
except requests.exceptions.ConnectionError as e:
print(f"\n❌ CONNECTION ERROR: Cannot reach webhook")
print(f"Details: {e}")
print("\nPossible causes:")
print("1. N8N server is not running on localhost:5678")
print("2. Webhook ID is incorrect")
print("3. Firewall blocking the connection")
except requests.exceptions.Timeout as e:
print(f"\n⏰ TIMEOUT ERROR: Webhook took too long to respond")
print(f"Details: {e}")
except Exception as e:
print(f"\n❌ UNEXPECTED ERROR: {e}")
def test_webhook_simple():
"""Simple ping test to check if webhook endpoint exists"""
webhook_url = "http://localhost:5678/webhook/2dc234d8-7217-454a-83e9-81afe5b4fe2d"
print(f"Simple connectivity test for: {webhook_url}")
print("-" * 50)
try:
# Simple GET request to see if endpoint exists
response = requests.get(webhook_url, timeout=10)
print(f"GET Response Status: {response.status_code}")
print(f"GET Response: {response.text[:200]}...")
except Exception as e:
print(f"GET request failed: {e}")
try:
# Simple POST with minimal data
response = requests.post(webhook_url, json={'test': 'ping'}, timeout=10)
print(f"POST Response Status: {response.status_code}")
print(f"POST Response: {response.text[:200]}...")
except Exception as e:
print(f"POST request failed: {e}")
if __name__ == "__main__":
print("🚀 Testing Social Ads Webhook Connectivity")
print("=" * 60)
# Run simple test first
test_webhook_simple()
print("\n" + "=" * 60)
# Run full test
test_social_ads_webhook()
print("\n" + "=" * 60)
print("Test completed!")

View File

@ -1,969 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Social Ads Generator - NetCop AI Hub</title>
<style>
/* Agent Frontend Template - Common Components CSS */
@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 {
--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);
}
html {
scrollbar-gutter: stable;
}
body {
background: var(--background);
color: var(--on-surface);
line-height: 1.5;
font-weight: 400;
margin: 0;
padding: 0;
}
/* Layout */
.agent-container {
margin: 0 auto;
padding: var(--spacing-lg);
max-width: 1600px;
}
.agent-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-xl);
}
.header-controls {
display: flex;
align-items: center;
gap: var(--spacing-lg);
}
.agent-grid {
display: flex;
gap: var(--spacing-lg);
align-items: flex-start;
flex-wrap: wrap;
margin-bottom: var(--spacing-lg);
}
/* Typography */
.agent-title {
font-size: 32px;
font-weight: 700;
color: var(--on-surface);
margin: 0;
letter-spacing: -0.5px;
}
.agent-subtitle {
font-size: 16px;
color: var(--on-surface-variant);
margin: 4px 0 0 0;
}
/* Widgets */
.agent-widget {
background: var(--surface);
border-radius: var(--radius-lg);
padding: var(--spacing-xl);
border: 1px solid var(--outline-variant);
box-shadow: var(--shadow-sm);
transition: all 0.2s ease;
display: flex;
flex-direction: column;
}
.widget-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-lg);
padding-bottom: var(--spacing-md);
border-bottom: 1px solid var(--outline-variant);
}
.widget-title {
font-size: 18px;
font-weight: 600;
color: var(--on-surface);
margin: 0;
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);
}
.widget-content {
flex: 1;
display: flex;
flex-direction: column;
gap: var(--spacing-md);
}
/* Widget Sizes */
.widget-large {
flex: 1;
min-width: 400px;
}
.widget-small {
flex: 0 0 280px;
}
.widget-wide {
flex: 1 1 100%;
width: 100%;
}
/* Wallet Card */
.wallet-card {
background: linear-gradient(135deg, #000000 0%, #333333 100%);
color: white;
border-radius: var(--radius-md);
padding: var(--spacing-md);
border: none;
box-shadow: var(--shadow-md);
position: relative;
overflow: hidden;
margin-bottom: 0;
min-height: auto;
}
.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(30px, -30px);
}
.wallet-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-sm);
}
.wallet-title {
font-size: 14px;
font-weight: 600;
margin: 0;
opacity: 0.9;
}
.wallet-icon {
font-size: 20px;
}
.balance-display {
margin-bottom: 0;
}
.balance-amount {
font-size: 24px;
font-weight: 700;
line-height: 1;
margin-bottom: 0;
letter-spacing: -0.5px;
}
.balance-label {
font-size: 12px;
opacity: 0.8;
font-weight: 400;
}
.wallet-topup-btn {
width: 100%;
padding: 8px 16px;
background: linear-gradient(135deg, #4f46e5, #7c3aed);
color: white;
border: none;
border-radius: 8px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
margin-top: 12px;
}
.wallet-topup-btn:hover {
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
/* Processing Status */
.processing-status {
background: var(--surface);
border: 1px solid var(--outline-variant);
border-radius: var(--radius-lg);
padding: var(--spacing-xl);
text-align: center;
display: none;
}
.processing-status.active {
display: block;
}
.status-icon {
font-size: 48px;
margin-bottom: var(--spacing-md);
animation: pulse 2s infinite;
}
.status-title {
font-size: 18px;
font-weight: 600;
color: var(--on-surface);
margin-bottom: var(--spacing-sm);
}
.status-text {
font-size: 14px;
color: var(--on-surface-variant);
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Quick Agent Access Panel */
.quick-agent-toggle {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-md) var(--spacing-lg);
background: var(--surface);
border: 1px solid var(--outline-variant);
border-radius: var(--radius-md);
color: var(--on-surface);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
position: relative;
width: 100%;
justify-content: center;
}
.quick-agent-toggle:hover {
background: var(--surface-variant);
border-color: var(--primary);
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
.quick-agent-toggle.active {
background: var(--primary);
color: white;
border-color: var(--primary);
}
.toggle-icon {
font-size: 16px;
transition: transform 0.2s ease;
}
.quick-agent-toggle.active .toggle-icon {
transform: rotate(180deg);
}
.quick-agents-panel {
position: fixed;
top: 0;
right: 0;
width: min(400px, 90vw);
height: 100vh;
background: var(--surface);
border-left: 1px solid var(--outline);
box-shadow: var(--shadow-lg);
z-index: 1000;
transform: translateX(100%);
transition: transform 0.3s ease;
overflow-y: auto;
}
.quick-agents-panel.active {
transform: translateX(0);
}
.quick-agents-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-lg);
border-bottom: 1px solid var(--outline-variant);
background: var(--surface-variant);
}
.quick-agents-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: var(--on-surface);
}
.close-panel {
background: none;
border: none;
font-size: 24px;
color: var(--on-surface-variant);
cursor: pointer;
padding: 4px;
border-radius: var(--radius-sm);
transition: all 0.2s ease;
}
.close-panel:hover {
background: var(--surface);
color: var(--on-surface);
}
.quick-agents-grid {
padding: var(--spacing-lg);
display: flex;
flex-direction: column;
gap: var(--spacing-md);
}
.quick-agent-card {
display: flex;
align-items: center;
gap: var(--spacing-md);
padding: var(--spacing-md);
border: 1px solid var(--outline-variant);
border-radius: var(--radius-md);
text-decoration: none;
color: var(--on-surface);
transition: all 0.2s ease;
background: var(--surface);
}
.quick-agent-card:hover {
background: var(--surface-variant);
border-color: var(--primary);
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
.agent-icon {
font-size: 24px;
width: 48px;
height: 48px;
display: flex;
align-items: center;
justify-content: center;
background: var(--surface-variant);
border-radius: var(--radius-md);
flex-shrink: 0;
}
.agent-info {
flex: 1;
}
.agent-info h4 {
margin: 0 0 4px 0;
font-size: 14px;
font-weight: 600;
color: var(--on-surface);
}
.agent-info p {
margin: 0;
font-size: 12px;
color: var(--on-surface-variant);
line-height: 1.4;
}
.agent-price {
font-size: 12px;
font-weight: 600;
color: var(--primary);
background: rgba(0, 0, 0, 0.05);
padding: 2px 8px;
border-radius: var(--radius-sm);
margin-top: 4px;
display: inline-block;
}
.quick-agents-footer {
padding: var(--spacing-md) var(--spacing-lg);
border-top: 1px solid var(--outline-variant);
background: var(--surface-variant);
}
.view-all-agents {
display: block;
text-align: center;
padding: var(--spacing-md);
background: var(--primary);
color: white;
text-decoration: none;
border-radius: var(--radius-md);
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
}
.view-all-agents:hover {
background: #333333;
transform: translateY(-1px);
box-shadow: var(--shadow-sm);
}
.quick-agents-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.quick-agents-overlay.active {
opacity: 1;
visibility: visible;
}
/* Placeholder Sections */
.placeholder-section {
background: var(--surface);
border: 2px dashed var(--outline);
border-radius: var(--radius-lg);
padding: var(--spacing-xl);
text-align: center;
color: var(--on-surface-variant);
min-height: 200px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-bottom: var(--spacing-lg);
}
.placeholder-title {
font-size: 18px;
font-weight: 600;
margin-bottom: var(--spacing-sm);
color: var(--on-surface);
}
.placeholder-description {
font-size: 14px;
line-height: 1.5;
max-width: 400px;
}
.placeholder-example {
background: var(--surface-variant);
border-radius: var(--radius-sm);
padding: var(--spacing-sm);
margin-top: var(--spacing-md);
font-size: 12px;
font-family: monospace;
color: var(--on-surface-variant);
}
/* Info List - For How It Works */
.info-list {
list-style: none;
padding: 0;
margin: 0;
counter-reset: step-counter;
}
.info-list li {
padding: var(--spacing-sm) 0;
color: var(--on-surface-variant);
font-size: 14px;
border-bottom: 1px solid var(--outline-variant);
position: relative;
padding-left: var(--spacing-lg);
}
.info-list li:last-child {
border-bottom: none;
}
.info-list li::before {
content: counter(step-counter);
counter-increment: step-counter;
position: absolute;
left: 0;
top: var(--spacing-sm);
background: var(--primary);
color: white;
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
}
/* Toast Notifications */
.toast {
position: fixed;
top: 20px;
right: 20px;
background: var(--surface);
border: 1px solid var(--outline);
border-radius: var(--radius-md);
padding: var(--spacing-md) var(--spacing-lg);
box-shadow: var(--shadow-lg);
z-index: 1000;
max-width: 400px;
font-size: 14px;
font-weight: 500;
transform: translateX(100%);
transition: transform 0.3s ease;
}
.toast.show {
transform: translateX(0);
}
.toast.success {
border-color: var(--success);
background: #f0fdf4;
color: #16a34a;
}
.toast.error {
border-color: var(--error);
background: #fef2f2;
color: #dc2626;
}
/* Responsive Design */
@media (max-width: 768px) {
.agent-container {
padding: var(--spacing-md);
}
.agent-header {
flex-direction: column;
align-items: flex-start;
gap: var(--spacing-sm);
}
.header-controls {
width: 100%;
justify-content: space-between;
}
.agent-title {
font-size: 24px;
}
.agent-grid {
flex-direction: column;
gap: var(--spacing-md);
}
.widget-large,
.widget-small {
min-width: auto;
flex: none;
}
.balance-amount {
font-size: 20px;
}
.quick-agents-panel {
width: 100%;
max-width: 100%;
right: 0;
left: 0;
}
}
</style>
</head>
<body>
<div class="agent-container">
<!-- Agent Header Component -->
<div class="agent-header">
<div>
<h1 class="agent-title">Social Ads Generator</h1>
<p class="agent-subtitle">Create compelling social media advertisements with AI-powered content generation</p>
</div>
<div class="header-controls">
<!-- Wallet Card Component -->
<div class="wallet-card widget-small">
<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">25.50</span> AED
</div>
<div class="balance-label">Available Balance</div>
</div>
<button type="button" class="wallet-topup-btn" onclick="showToast('Top-up feature demo!', 'success')">
💳 Top Up Wallet
</button>
</div>
</div>
</div>
<!-- Quick Agent Access Panel Overlay -->
<div class="quick-agents-overlay" id="quickAgentsOverlay" onclick="closeQuickAgents()"></div>
<!-- Quick Agent Access Panel -->
<div class="quick-agents-panel" id="quickAgentsPanel">
<div class="quick-agents-header">
<h3>Quick Access</h3>
<button class="close-panel" onclick="closeQuickAgents()" aria-label="Close panel">×</button>
</div>
<div class="quick-agents-grid">
<!-- Sample Agent Cards -->
<a href="#" class="quick-agent-card">
<div class="agent-icon">🌤️</div>
<div class="agent-info">
<h4>Weather Reporter</h4>
<p>Get real-time weather data</p>
<span class="agent-price">2.00 AED</span>
</div>
</a>
<a href="#" 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.00 AED</span>
</div>
</a>
<a href="#" class="quick-agent-card">
<div class="agent-icon">💼</div>
<div class="agent-info">
<h4>Job Posting Generator</h4>
<p>Create professional job postings</p>
<span class="agent-price">4.00 AED</span>
</div>
</a>
</div>
<div class="quick-agents-footer">
<a href="#" class="view-all-agents">View All Agents</a>
</div>
</div>
<!-- Agent Grid - SOCIAL ADS GENERATOR SPECIFIC CONTENT -->
<div class="agent-grid">
<div class="placeholder-section widget-large" style="flex: 1; margin-right: var(--spacing-lg);">
<div class="placeholder-title">📱 Social Ads Generator Form</div>
<div class="placeholder-description">
This section would contain the Social Ads Generator form with inputs for business details, platform selection, target audience, and ad preferences.
</div>
<div class="placeholder-example">
Examples: Business name input, platform radio buttons (Facebook, Instagram, Twitter), ad format selection, target audience textarea, tone checkboxes, etc.
</div>
</div>
<!-- How It Works Widget - Common Pattern -->
<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">
<ol class="info-list">
<li>Enter business and product details</li>
<li>Choose platform and ad format</li>
<li>Define target audience and tone</li>
<li>Get optimized ad content and copy</li>
</ol>
<!-- Quick Agents Toggle Button -->
<button class="quick-agent-toggle" onclick="toggleQuickAgents()"
title="Quick access to other agents"
aria-label="Open quick access panel for other AI agents"
aria-expanded="false"
aria-controls="quickAgentsPanel"
style="margin-top: var(--spacing-md);">
<span class="toggle-icon" aria-hidden="true">🚀</span>
<span class="toggle-text">Explore Other Agents</span>
</button>
</div>
</div>
</div>
<!-- Processing Status Component -->
<div class="agent-grid">
<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">Creating your social media ad...</div>
<div class="status-text" id="statusText">Please wait while we generate compelling ad content...</div>
</div>
</div>
</div>
<!-- Results Section - SOCIAL ADS GENERATOR SPECIFIC RESULTS -->
<div class="agent-grid">
<div class="placeholder-section widget-wide">
<div class="placeholder-title">📱 Generated Social Ad Results</div>
<div class="placeholder-description">
This section would display the generated social media ad content with platform-specific formatting, headlines, copy, and optimization suggestions.
</div>
<div class="placeholder-example">
Examples: Ad headline, body copy, call-to-action button text, hashtag suggestions, platform optimization tips, copy/download buttons, etc.
</div>
</div>
</div>
<!-- Demo Controls -->
<div class="agent-grid" style="margin-top: var(--spacing-xl); border-top: 1px solid var(--outline-variant); padding-top: var(--spacing-lg);">
<div class="agent-widget widget-wide">
<div class="widget-header">
<h3 class="widget-title">
<span class="widget-icon">🧪</span>
Demo Controls
</h3>
</div>
<div class="widget-content">
<p style="margin-bottom: var(--spacing-md); color: var(--on-surface-variant);">
Test the common components and interactions:
</p>
<div style="display: flex; gap: var(--spacing-md); flex-wrap: wrap;">
<button onclick="showProcessing()" style="padding: var(--spacing-sm) var(--spacing-md); background: var(--primary); color: white; border: none; border-radius: var(--radius-sm); cursor: pointer;">
Show Processing
</button>
<button onclick="hideProcessing()" style="padding: var(--spacing-sm) var(--spacing-md); background: var(--surface); color: var(--on-surface); border: 1px solid var(--outline); border-radius: var(--radius-sm); cursor: pointer;">
Hide Processing
</button>
<button onclick="showToast('Success message!', 'success')" style="padding: var(--spacing-sm) var(--spacing-md); background: var(--success); color: white; border: none; border-radius: var(--radius-sm); cursor: pointer;">
Success Toast
</button>
<button onclick="showToast('Error message!', 'error')" style="padding: var(--spacing-sm) var(--spacing-md); background: var(--error); color: white; border: none; border-radius: var(--radius-sm); cursor: pointer;">
Error Toast
</button>
</div>
</div>
</div>
</div>
</div>
<script>
// Agent Frontend Template - Common JavaScript Utilities
// Quick Agent Access Panel Functions
function toggleQuickAgents() {
const panel = document.getElementById('quickAgentsPanel');
const overlay = document.getElementById('quickAgentsOverlay');
const toggle = document.querySelector('.quick-agent-toggle');
const isActive = panel.classList.contains('active');
if (isActive) {
// Close panel
panel.classList.remove('active');
overlay.classList.remove('active');
toggle.classList.remove('active');
toggle.setAttribute('aria-expanded', 'false');
panel.setAttribute('aria-hidden', 'true');
overlay.setAttribute('aria-hidden', 'true');
document.body.style.overflow = 'auto';
} else {
// Open panel
panel.classList.add('active');
overlay.classList.add('active');
toggle.classList.add('active');
toggle.setAttribute('aria-expanded', 'true');
panel.setAttribute('aria-hidden', 'false');
overlay.setAttribute('aria-hidden', 'false');
document.body.style.overflow = 'hidden';
}
}
function closeQuickAgents() {
const panel = document.getElementById('quickAgentsPanel');
const overlay = document.getElementById('quickAgentsOverlay');
const toggle = document.querySelector('.quick-agent-toggle');
if (panel && overlay && toggle) {
panel.classList.remove('active');
overlay.classList.remove('active');
toggle.classList.remove('active');
toggle.setAttribute('aria-expanded', 'false');
panel.setAttribute('aria-hidden', 'true');
overlay.setAttribute('aria-hidden', 'true');
document.body.style.overflow = 'auto';
}
}
// Toast Notification Function
function showToast(message, type = 'info') {
// Remove existing toasts
document.querySelectorAll('.toast').forEach(toast => toast.remove());
// Create new toast
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.textContent = message;
// Add to page
document.body.appendChild(toast);
// Show toast with animation
setTimeout(() => toast.classList.add('show'), 100);
// Auto remove after 3 seconds
setTimeout(() => {
toast.classList.remove('show');
setTimeout(() => toast.remove(), 300);
}, 3000);
}
// Processing Status Functions
function showProcessing() {
const processingStatus = document.getElementById('processingStatus');
processingStatus.style.display = 'block';
processingStatus.classList.add('active');
showToast('Processing started...', 'success');
}
function hideProcessing() {
const processingStatus = document.getElementById('processingStatus');
processingStatus.style.display = 'none';
processingStatus.classList.remove('active');
showToast('Processing stopped...', 'success');
}
// Wallet Balance Update Function
function updateWalletBalance(newBalance) {
if (newBalance !== undefined) {
const walletBalance = document.getElementById('walletBalance');
if (walletBalance) {
walletBalance.textContent = newBalance.toFixed(2);
}
showToast(`Wallet updated: ${newBalance.toFixed(2)} AED`, 'success');
}
}
// Copy to Clipboard Utility
function copyToClipboard(text, successMessage = 'Copied to clipboard!') {
navigator.clipboard.writeText(text).then(() => {
showToast(`📋 ${successMessage}`, 'success');
}).catch(() => {
showToast('Failed to copy to clipboard', 'error');
});
}
// Download as File Utility
function 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);
showToast(`💾 ${successMessage}`, 'success');
}
// Close panel on Escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeQuickAgents();
}
});
// Initialize accessibility features
document.addEventListener('DOMContentLoaded', function() {
// Set initial ARIA states
const quickAgentsButton = document.querySelector('.quick-agent-toggle');
if (quickAgentsButton) {
quickAgentsButton.setAttribute('aria-expanded', 'false');
}
const panel = document.getElementById('quickAgentsPanel');
const overlay = document.getElementById('quickAgentsOverlay');
if (panel) panel.setAttribute('aria-hidden', 'true');
if (overlay) overlay.setAttribute('aria-hidden', 'true');
// Show welcome message
setTimeout(() => {
showToast('Social Ads Generator template loaded!', 'success');
}, 500);
});
// Placeholder for agent-specific JavaScript
// ========================================
//
// Social Ads Generator specific functions would go here:
// - Form validation for business details
// - Platform selection handling
// - Target audience processing
// - Ad generation and display
// - Copy/download functionality for ads
//
// Example structure:
// function validateSocialAdsForm() { ... }
// function submitAdRequest() { ... }
// function displayGeneratedAd() { ... }
//
// ========================================
</script>
</body>
</html>