🎨 Add external service wrappers and social media previews

- External service wrapper system for JotForm, Zapier integrations
- Simple template-based approach (iframe, landing, redirect)
- Rich social media previews with Open Graph and Twitter Card tags
- Professional social preview image for branded sharing
- Updated documentation with usage examples

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude 2025-08-15 18:21:10 +05:30
parent 838927f316
commit cae8d4a8fe
10 changed files with 413 additions and 4 deletions

View File

@ -212,6 +212,57 @@ The platform supports 2 agent types:
- **Webhook Agents** - N8N integration with dynamic forms - **Webhook Agents** - N8N integration with dynamic forms
- **Direct Access Agents** - External forms (JotForm, etc.) with embedded interfaces - **Direct Access Agents** - External forms (JotForm, etc.) with embedded interfaces
## External Service Wrappers
**Simple template-based system for event invitations, demos, and external forms:**
**Configuration:** Edit `EXTERNAL_PAGES` dict in `core/views.py`:
```python
EXTERNAL_PAGES = {
'event-invitation': {
'title': 'Event Registration',
'external_url': 'https://form.jotform.com/252214924850455',
'template': 'iframe', # iframe, landing, or redirect
}
}
```
**Templates Available:**
- `templates/wrapper/iframe.html` - Full-screen iframe embed
- `templates/wrapper/landing.html` - Branded landing page with embed
- `templates/wrapper/redirect.html` - Auto-redirect with countdown
**Access:** `/{page-name}/` (e.g., `/event-invitation/`)
**Features:**
- Rate limiting protection
- Consistent branding
- Mobile responsive
- No database needed
## Social Media Integration
**Rich social media previews implemented in `templates/base.html`:**
**Open Graph Tags:**
- `og:title` - Page title for social sharing
- `og:description` - Page description
- `og:image` - Preview image (`static/img/social-preview.png`)
- `og:url` - Canonical page URL
- `og:site_name` - "Quantum Tasks AI"
**Twitter Card Tags:**
- `twitter:card` - Large image format
- `twitter:title/description/image` - Twitter-specific metadata
**Custom Per-Page:** Override blocks in templates:
```django
{% block og_title %}Custom Page Title{% endblock %}
{% block meta_description %}Custom description{% endblock %}
```
**Result:** Rich previews on WhatsApp, Discord, Twitter, LinkedIn with branded image and professional descriptions.
## Production Deployment ## Production Deployment
**Railway Configuration:** **Railway Configuration:**
@ -269,6 +320,8 @@ The platform supports 2 agent types:
- **Direct Access Agents (4)**: CyberSec Career Navigator, AI Brand Strategist, Lean Six Sigma Expert, SWOT Analysis Expert - **Direct Access Agents (4)**: CyberSec Career Navigator, AI Brand Strategist, Lean Six Sigma Expert, SWOT Analysis Expert
**Latest Changes:** **Latest Changes:**
- **External service wrapper system** - Simple template-based system for JotForm, Zapier, event invitations
- **Social media meta tags** - Rich previews with Open Graph and Twitter Card tags
- **View separation completed** - Split large views.py into focused modules (api, chat, web, direct access) - **View separation completed** - Split large views.py into focused modules (api, chat, web, direct access)
- **Restored digital-branding.css** from git history with proper design system integration - **Restored digital-branding.css** from git history with proper design system integration
- **Added SWOT Analysis Expert** with proper category assignment (analysis) - **Added SWOT Analysis Expert** with proper category assignment (analysis)

View File

@ -0,0 +1,16 @@
{
"slug": "demo-assistant",
"name": "Demo AI Assistant",
"short_description": "Demo AI assistant for testing direct access agent functionality",
"description": "A simple demo AI assistant that showcases the direct access agent system. Perfect for testing and demonstration purposes.",
"category": "analysis",
"price": 0.0,
"agent_type": "form",
"system_type": "direct_access",
"form_schema": {
"fields": []
},
"webhook_url": "https://agent.jotform.com/019865a942ab7fa5b5b743a5fd2abe09e345",
"access_url_name": "agents:direct_access_handler",
"display_url_name": "agents:direct_access_display"
}

View File

@ -9,4 +9,7 @@ urlpatterns = [
path('pricing/', views.pricing_view, name='pricing'), path('pricing/', views.pricing_view, name='pricing'),
path('contact/', views.contact_form_view, name='contact_form'), path('contact/', views.contact_form_view, name='contact_form'),
path('health/', views.health_check_view, name='health_check'), path('health/', views.health_check_view, name='health_check'),
# External service wrapper pages
path('<str:page_name>/', views.external_page_view, name='external_page'),
] ]

View File

@ -1,7 +1,7 @@
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib import messages from django.contrib import messages
from django.http import JsonResponse from django.http import JsonResponse, Http404
from django.core.mail import send_mail from django.core.mail import send_mail
from django.conf import settings from django.conf import settings
from django_ratelimit.decorators import ratelimit from django_ratelimit.decorators import ratelimit
@ -282,3 +282,61 @@ def health_check_view(request):
# Always return 200 - we're healthy if Django is running # Always return 200 - we're healthy if Django is running
return JsonResponse(health_data, status=200) return JsonResponse(health_data, status=200)
# Simple external service wrapper configurations
EXTERNAL_PAGES = {
'event-invitation': {
'title': 'Event Registration',
'description': 'Register for our upcoming event',
'external_url': 'https://form.jotform.com/252214924850455',
'template': 'iframe', # iframe, landing, or redirect
},
'demo-form': {
'title': 'Product Demo Request',
'description': 'Schedule a personalized demo of our platform',
'external_url': 'https://calendly.com/your-demo-link',
'template': 'landing',
},
'consultation': {
'title': 'Free Consultation',
'external_url': 'https://www.jotform.com/consultation-form',
'template': 'redirect',
},
}
@ratelimit(key='ip', rate='30/m', method='GET', block=False)
def external_page_view(request, page_name):
"""
Simple external service wrapper view.
Just renders templates with external URLs - no database needed.
"""
# Check rate limiting
if getattr(request, 'limited', False):
logger.warning(f"External page rate limit exceeded for IP {request.META.get('REMOTE_ADDR')}")
messages.warning(request, 'Too many requests. Please wait a moment.')
return redirect('core:homepage')
# Get page config
page_config = EXTERNAL_PAGES.get(page_name)
if not page_config:
raise Http404("Page not found")
# Choose template
template_map = {
'iframe': 'wrapper/iframe.html',
'landing': 'wrapper/landing.html',
'redirect': 'wrapper/redirect.html',
}
template_name = template_map.get(page_config['template'], 'wrapper/iframe.html')
context = {
'page_title': page_config['title'],
'page_description': page_config.get('description', ''),
'external_url': page_config['external_url'],
'user_balance': request.user.wallet_balance if request.user.is_authenticated else 0,
}
return render(request, template_name, context)

View File

@ -156,25 +156,29 @@ git push
- **Zero URL Configuration**: Dynamic routing based on JSON config properties - **Zero URL Configuration**: Dynamic routing based on JSON config properties
- **Zero Templates**: Single generic template works for all direct access agents - **Zero Templates**: Single generic template works for all direct access agents
- **Zero Database Setup**: Pure file-based loading with intelligent caching - **Zero Database Setup**: Pure file-based loading with intelligent caching
- **Zero Error Handling**: Automatic webhook error detection and user feedback
### Agent Type Handling ### Agent Type Handling
- **Webhook Agents**: Automatically generate dynamic forms from `form_schema` - **Webhook Agents**: Automatically generate dynamic forms from `form_schema`
- **Direct Access Agents**: Automatically handle payment processing + external redirect - **Direct Access Agents**: Automatically handle payment processing + external redirect
- **Both Types**: Work with only JSON configuration, no additional code - **Both Types**: Work with only JSON configuration, no additional code
- **Error Handling**: Automatically detects webhook failures (OpenAI quota, N8N issues, timeouts) and shows user-friendly messages
### Development Workflow Comparison ### Development Workflow Comparison
```bash ```bash
# ❌ Old Complex Way (5+ steps) # ❌ Old Complex Way (6+ steps)
1. Create JSON config 1. Create JSON config
2. Write Python view functions 2. Write Python view functions
3. Add URL routes 3. Add URL routes
4. Create HTML templates 4. Create HTML templates
5. Update view imports 5. Update view imports
6. Test and debug 6. Write error handling code
7. Test and debug
# ✅ New Simple Way (1 step) # ✅ New Simple Way (1 step)
1. Create JSON config 1. Create JSON config
# Done! Everything else is automatic 🎉 # Done! Everything else is automatic 🎉
# - Forms, routing, error handling, user feedback all automated
``` ```
### How Both Agent Types Work Now ### How Both Agent Types Work Now
@ -182,6 +186,7 @@ git push
**Webhook Agents:** **Webhook Agents:**
- JSON config → Dynamic form via `agent_detail_view` - JSON config → Dynamic form via `agent_detail_view`
- Form submission → N8N webhook → Results display - Form submission → N8N webhook → Results display
- Automatic error detection and user feedback
- No individual Python code needed - No individual Python code needed
**Direct Access Agents:** **Direct Access Agents:**
@ -189,6 +194,45 @@ git push
- Payment → Generic iframe display - Payment → Generic iframe display
- No individual Python code needed - No individual Python code needed
## Automatic Error Handling 🛡️
The platform now includes **completely automated error handling** for all agents:
### What's Automatically Handled
- **N8N Webhook Failures**: Service down, timeouts, configuration errors
- **AI Service Limits**: OpenAI quota exceeded, rate limits, API errors
- **Network Issues**: Connection failures, DNS problems, timeouts
- **Invalid Responses**: Malformed data, unexpected formats
### User Experience
- **Clear Error Messages**: "Agent is temporarily unavailable. Please try again later."
- **Persistent Display**: Error shown in results area (won't disappear like notifications)
- **No Technical Jargon**: Simple, friendly language instead of HTTP status codes
### Developer Benefits
- **Zero Configuration**: No error handling code needed in JSON configs
- **Automatic Detection**: System distinguishes between success and various failure types
- **Consistent UX**: All agents have identical error handling behavior
- **Debug Friendly**: Technical errors still logged to console for troubleshooting
### Examples of Handled Errors
```json
// N8N Response (OpenAI quota exceeded)
{
"errorMessage": "You exceeded your current quota",
"errorDetails": {"httpCode": "429"}
}
// → User sees: "Agent is temporarily unavailable. Please try again later."
// HTTP 500 from webhook
// → User sees: "Agent is temporarily unavailable. Please try again later."
// Connection timeout
// → User sees: "Agent is temporarily unavailable. Please try again later."
```
All technical details are logged for debugging, but users always see the same friendly message.
## Custom Integration (Advanced) ## Custom Integration (Advanced)
For agents needing custom behavior, add views to appropriate modules: For agents needing custom behavior, add views to appropriate modules:
@ -229,4 +273,4 @@ Then add URL routes in `agents/urls.py` and update marketplace template if neede
--- ---
*Last updated: 2025-01-14* *Last updated: 2025-01-15*

File diff suppressed because one or more lines are too long

View File

@ -6,6 +6,25 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Quantum Tasks AI - AI Platform{% endblock %}</title> <title>{% block title %}Quantum Tasks AI - AI Platform{% endblock %}</title>
<!-- SEO Meta Tags -->
<meta name="description" content="{% block meta_description %}Quantum Tasks AI - Advanced AI platform for automation and intelligent task management. Access powerful AI agents for your business needs.{% endblock %}">
<meta name="keywords" content="AI, artificial intelligence, automation, task management, AI agents, quantum computing">
<meta name="author" content="Quantum Tasks AI">
<!-- Open Graph Meta Tags for Social Media -->
<meta property="og:title" content="{% block og_title %}{% block title %}Quantum Tasks AI - AI Platform{% endblock %}{% endblock %}">
<meta property="og:description" content="{% block og_description %}{% block meta_description %}Quantum Tasks AI - Advanced AI platform for automation and intelligent task management. Access powerful AI agents for your business needs.{% endblock %}{% endblock %}">
<meta property="og:image" content="{% block og_image %}{% static 'img/social-preview.png' %}{% endblock %}">
<meta property="og:url" content="{% block og_url %}{{ request.build_absolute_uri }}{% endblock %}">
<meta property="og:type" content="{% block og_type %}website{% endblock %}">
<meta property="og:site_name" content="Quantum Tasks AI">
<!-- Twitter Card Meta Tags -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="{% block twitter_title %}{% block og_title %}{% block title %}Quantum Tasks AI - AI Platform{% endblock %}{% endblock %}{% endblock %}">
<meta name="twitter:description" content="{% block twitter_description %}{% block og_description %}{% block meta_description %}Quantum Tasks AI - Advanced AI platform for automation and intelligent task management. Access powerful AI agents for your business needs.{% endblock %}{% endblock %}{% endblock %}">
<meta name="twitter:image" content="{% block twitter_image %}{% block og_image %}{% static 'img/social-preview.png' %}{% endblock %}{% endblock %}">
<!-- Unified Font Loading - Single Source of Truth --> <!-- Unified Font Loading - Single Source of Truth -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

View File

@ -0,0 +1,42 @@
{% extends 'base.html' %}
{% block title %}{{ page_title|default:"External Service" }} - Quantum Tasks AI{% endblock %}
{% block extra_css %}
<style>
/* Full-width iframe styling */
.main-container {
max-width: none;
padding: 0;
height: calc(100vh - 80px);
}
.iframe-container {
width: 100%;
height: 100%;
}
.iframe-container iframe {
width: 100%;
height: 100%;
border: none;
display: block;
}
/* Hide footer for full-screen */
.footer {
display: none !important;
}
</style>
{% endblock %}
{% block content %}
<div class="iframe-container">
<iframe
src="{{ external_url }}"
frameborder="0"
scrolling="auto"
title="{{ page_title|default:'External Service' }}">
</iframe>
</div>
{% endblock %}

View File

@ -0,0 +1,68 @@
{% extends 'base.html' %}
{% block title %}{{ page_title|default:"External Service" }} - Quantum Tasks AI{% endblock %}
{% block extra_css %}
<style>
.landing-page {
min-height: 80vh;
padding: 2rem 0;
}
.landing-header {
text-align: center;
margin-bottom: 3rem;
}
.landing-title {
font-size: 2.5rem;
font-weight: 700;
color: #1a1a1a;
margin-bottom: 1rem;
}
.landing-description {
font-size: 1.25rem;
color: #666;
max-width: 800px;
margin: 0 auto;
}
.external-service-embed {
width: 100%;
min-height: 600px;
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.external-service-embed iframe {
width: 100%;
height: 100%;
border: none;
}
</style>
{% endblock %}
{% block content %}
<div class="landing-page">
<div class="container">
<div class="landing-header">
<h1 class="landing-title">{{ page_title|default:"External Service" }}</h1>
{% if page_description %}
<p class="landing-description">{{ page_description }}</p>
{% endif %}
</div>
<div class="external-service-embed">
<iframe
src="{{ external_url }}"
frameborder="0"
scrolling="auto"
title="{{ page_title|default:'External Service' }}">
</iframe>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,105 @@
{% extends 'base.html' %}
{% block title %}Redirecting to {{ page_title|default:"External Service" }} - Quantum Tasks AI{% endblock %}
{% block extra_css %}
<style>
.redirect-page {
min-height: 80vh;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.redirect-content {
max-width: 600px;
padding: 3rem 2rem;
background: white;
border-radius: 12px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
}
.redirect-title {
font-size: 2rem;
font-weight: 700;
color: #1a1a1a;
margin-bottom: 1rem;
}
.redirect-timer {
font-size: 1.25rem;
color: #667eea;
font-weight: 600;
margin-bottom: 2rem;
}
.redirect-button {
display: inline-block;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 1rem 2rem;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
margin: 0.5rem;
}
.redirect-button:hover {
color: white;
text-decoration: none;
}
.spinner {
width: 3rem;
height: 3rem;
border: 3px solid #f3f3f3;
border-top: 3px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
{% endblock %}
{% block content %}
<div class="redirect-page">
<div class="redirect-content">
<div class="spinner"></div>
<h1 class="redirect-title">Redirecting to {{ page_title|default:"External Service" }}</h1>
<div class="redirect-timer">
Redirecting in <span id="countdown">3</span> seconds...
</div>
<div>
<a href="{{ external_url }}" class="redirect-button">
Continue Now
</a>
<a href="{% url 'core:homepage' %}" class="redirect-button" style="background: #6c757d;">
Go Back Home
</a>
</div>
</div>
</div>
<script>
let countdown = 3;
const countdownElement = document.getElementById('countdown');
const timer = setInterval(() => {
countdown--;
countdownElement.textContent = countdown;
if (countdown <= 0) {
clearInterval(timer);
window.location.href = "{{ external_url }}";
}
}, 1000);
</script>
{% endblock %}