diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index a5ed392..c77a808 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index e061640..c0990a3 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 5a69659..5ef2e2f 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2a36fd6..0508c2f 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index 8c38f3f..175ab6f 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,3 +1,26 @@ from django.contrib import admin +from .models import Fanpage, Flow, Node, Edge, ChatSession, MessageLog -# Register your models here. +@admin.register(Fanpage) +class FanpageAdmin(admin.ModelAdmin): + list_display = ('name', 'page_id', 'is_active', 'created_at') + +@admin.register(Flow) +class FlowAdmin(admin.ModelAdmin): + list_display = ('name', 'fanpage', 'is_default', 'created_at') + +@admin.register(Node) +class NodeAdmin(admin.ModelAdmin): + list_display = ('name', 'flow', 'node_type', 'is_start_node') + +@admin.register(Edge) +class EdgeAdmin(admin.ModelAdmin): + list_display = ('source_node', 'target_node', 'condition') + +@admin.register(ChatSession) +class ChatSessionAdmin(admin.ModelAdmin): + list_display = ('psid', 'fanpage', 'current_node', 'updated_at') + +@admin.register(MessageLog) +class MessageLogAdmin(admin.ModelAdmin): + list_display = ('session', 'sender_type', 'timestamp') \ No newline at end of file diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..fc4d89e --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,87 @@ +# Generated by Django 5.2.7 on 2026-02-07 17:11 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Fanpage', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('page_id', models.CharField(max_length=255, unique=True)), + ('access_token', models.TextField()), + ('verify_token', models.CharField(help_text='Token for Facebook Webhook verification', max_length=255)), + ('is_active', models.BooleanField(default=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ], + ), + migrations.CreateModel( + name='ChatSession', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('psid', models.CharField(help_text='Facebook Page Scoped ID of the user', max_length=255)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('fanpage', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.fanpage')), + ], + ), + migrations.CreateModel( + name='Flow', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('description', models.TextField(blank=True)), + ('is_default', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('fanpage', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='flows', to='core.fanpage')), + ], + ), + migrations.CreateModel( + name='MessageLog', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('sender_type', models.CharField(choices=[('user', 'User'), ('bot', 'Bot')], max_length=10)), + ('message_text', models.TextField()), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('session', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='logs', to='core.chatsession')), + ], + ), + migrations.CreateModel( + name='Node', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='Internal name for this step', max_length=255)), + ('node_type', models.CharField(choices=[('text', 'Text Message'), ('buttons', 'Buttons/Quick Replies'), ('image', 'Image')], default='text', max_length=20)), + ('content', models.JSONField(help_text='Stores the message text, button labels, etc.')), + ('is_start_node', models.BooleanField(default=False)), + ('flow', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='nodes', to='core.flow')), + ], + ), + migrations.CreateModel( + name='Edge', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('condition', models.CharField(help_text='Keyword or button payload that triggers this edge', max_length=255)), + ('flow', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='edges', to='core.flow')), + ('source_node', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='outgoing_edges', to='core.node')), + ('target_node', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='incoming_edges', to='core.node')), + ], + ), + migrations.AddField( + model_name='chatsession', + name='current_node', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.node'), + ), + migrations.AlterUniqueTogether( + name='chatsession', + unique_together={('psid', 'fanpage')}, + ), + ] diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..3e42eee Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 71a8362..a511c6b 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,61 @@ from django.db import models -# Create your models here. +class Fanpage(models.Model): + name = models.CharField(max_length=255) + page_id = models.CharField(max_length=255, unique=True) + access_token = models.TextField() + verify_token = models.CharField(max_length=255, help_text="Token for Facebook Webhook verification") + is_active = models.BooleanField(default=True) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return self.name + +class Flow(models.Model): + name = models.CharField(max_length=255) + description = models.TextField(blank=True) + fanpage = models.ForeignKey(Fanpage, on_delete=models.CASCADE, related_name='flows') + is_default = models.BooleanField(default=False) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"{self.name} ({self.fanpage.name})" + +class Node(models.Model): + NODE_TYPES = ( + ('text', 'Text Message'), + ('buttons', 'Buttons/Quick Replies'), + ('image', 'Image'), + ) + flow = models.ForeignKey(Flow, on_delete=models.CASCADE, related_name='nodes') + name = models.CharField(max_length=255, help_text="Internal name for this step") + node_type = models.CharField(max_length=20, choices=NODE_TYPES, default='text') + content = models.JSONField(help_text="Stores the message text, button labels, etc.") + is_start_node = models.BooleanField(default=False) + + def __str__(self): + return f"{self.name} [{self.node_type}]" + +class Edge(models.Model): + flow = models.ForeignKey(Flow, on_delete=models.CASCADE, related_name='edges') + source_node = models.ForeignKey(Node, on_delete=models.CASCADE, related_name='outgoing_edges') + target_node = models.ForeignKey(Node, on_delete=models.CASCADE, related_name='incoming_edges') + condition = models.CharField(max_length=255, help_text="Keyword or button payload that triggers this edge") + + def __str__(self): + return f"{self.source_node.name} -> {self.target_node.name} on '{self.condition}'" + +class ChatSession(models.Model): + psid = models.CharField(max_length=255, help_text="Facebook Page Scoped ID of the user") + fanpage = models.ForeignKey(Fanpage, on_delete=models.CASCADE) + current_node = models.ForeignKey(Node, on_delete=models.SET_NULL, null=True, blank=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + unique_together = ('psid', 'fanpage') + +class MessageLog(models.Model): + session = models.ForeignKey(ChatSession, on_delete=models.CASCADE, related_name='logs') + sender_type = models.CharField(max_length=10, choices=(('user', 'User'), ('bot', 'Bot'))) + message_text = models.TextField() + timestamp = models.DateTimeField(auto_now_add=True) \ No newline at end of file diff --git a/core/templates/base.html b/core/templates/base.html index 1e7e5fb..1739522 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -1,25 +1,53 @@ - - - {% block title %}Knowledge Base{% endblock %} - {% if project_description %} - - - - {% endif %} - {% if project_image_url %} - - - {% endif %} - {% load static %} - - {% block head %}{% endblock %} + + + {% block title %}FlowBot | Facebook Automation{% endblock %} + + {% load static %} + + {% block head %}{% endblock %} - - {% block content %}{% endblock %} - + - + {% block content %}{% endblock %} + + + + + {% block scripts %}{% endblock %} + + \ No newline at end of file diff --git a/core/templates/core/dashboard.html b/core/templates/core/dashboard.html new file mode 100644 index 0000000..f1402aa --- /dev/null +++ b/core/templates/core/dashboard.html @@ -0,0 +1,110 @@ +{% extends 'base.html' %} + +{% block title %}Dashboard | FlowBot{% endblock %} + +{% block content %} +
+
+
+

Welcome back, {{ user.username }}!

+

Here's what's happening with your bots today.

+
+ +
+ +
+
+
+
Active Pages
+
{{ fanpage_count }}
+
+
+
+
+
Total Flows
+
{{ flow_count }}
+
+
+
+
+
Today's Messages
+
0
+
+
+
+ +
+
+
+
+
Connected Fanpages
+ View All +
+
+ + + + + + + + + + + {% for page in fanpages %} + + + + + + + {% empty %} + + + + {% endfor %} + +
Page NameStatusActive FlowActions
+
{{ page.name }}
+ ID: {{ page.page_id }} +
+ {% if page.is_active %} + Active + {% else %} + Paused + {% endif %} + + {% with page.flows.first as flow %} + {% if flow %}{{ flow.name }}{% else %}None{% endif %} + {% endwith %} + + Manage +
No pages connected yet.
+
+
+
+
+
+
Recent Activity
+
+ {% for log in recent_logs %} +
+
+ + {{ log.sender_type|upper }} + + {{ log.timestamp|timesince }} ago +
+

{{ log.message_text }}

+
+ {% empty %} +

No recent activity found.

+ {% endfor %} +
+
+
+
+
+{% endblock %} diff --git a/core/templates/core/fanpage_list.html b/core/templates/core/fanpage_list.html new file mode 100644 index 0000000..6076464 --- /dev/null +++ b/core/templates/core/fanpage_list.html @@ -0,0 +1,42 @@ +{% extends 'base.html' %} + +{% block title %}Fanpages | FlowBot{% endblock %} + +{% block content %} +
+
+

Your Fanpages

+ Add Fanpage +
+ +
+ {% for page in fanpages %} +
+
+
+ + {% if page.is_active %}Active{% else %}Inactive{% endif %} + + Created {{ page.created_at|date:"M d, Y" }} +
+

{{ page.name }}

+

Page ID: {{ page.page_id }}

+
+
+ {{ page.flows.count }} Flow(s) + Edit Settings +
+
+
+ {% empty %} +
+
+

No Fanpages found

+

Connect your first Facebook Page to start automating!

+ Connect Now +
+
+ {% endfor %} +
+
+{% endblock %} diff --git a/core/templates/core/flow_list.html b/core/templates/core/flow_list.html new file mode 100644 index 0000000..5c91e39 --- /dev/null +++ b/core/templates/core/flow_list.html @@ -0,0 +1,46 @@ +{% extends 'base.html' %} + +{% block title %}Flows | FlowBot{% endblock %} + +{% block content %} +
+
+

Conversation Flows

+ Create New Flow +
+ +
+ {% for flow in flows %} +
+
+
+

{{ flow.name }}

+ {% if flow.is_default %} + Default + {% endif %} +
+

{{ flow.description|default:"No description provided." }}

+
+
+ {{ flow.fanpage.name }} + {{ flow.nodes.count }} nodes +
+
+ Edit + +
+
+
+
+ {% empty %} +
+
+

No flows defined

+

Start by creating a conversation flow for one of your pages.

+ Create Flow +
+
+ {% endfor %} +
+
+{% endblock %} diff --git a/core/templates/core/index.html b/core/templates/core/index.html index faec813..407588e 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,145 +1,75 @@ -{% extends "base.html" %} - -{% block title %}{{ project_name }}{% endblock %} - -{% block head %} - - - - -{% endblock %} +{% extends 'base.html' %} +{% load static %} {% block content %} -
-
-

Analyzing your requirements and generating your app…

-
- Loading… +
+
+
+
+

Automate Your Facebook Conversations

+

Build powerful, multi-page chatbots with a simple flow builder. Scale your customer service 24/7 without breaking a sweat.

+ +
+
+
+
+
+ +
+
Smart Auto-Reply
+
+
+ Customer + "What are your opening hours?" +
+
+ FlowBot + "Hi there! We are open from 9 AM to 6 PM daily. Would you like to see our menu?" +
+
+
+
-

AppWizzy AI is collecting your requirements and applying the first changes.

-

This page will refresh automatically as the plan is implemented.

-

- Runtime: Django {{ django_version }} · Python {{ python_version }} - — UTC {{ current_time|date:"Y-m-d H:i:s" }} -

-
-
- -{% endblock %} \ No newline at end of file + + +
+
+
+

Why Choose FlowBot?

+

The ultimate tool for multi-fanpage management.

+
+
+
+
+
+ +
+

Multi-Page Support

+

Connect and manage unlimited Fanpages from a single dashboard. Each page can have its own unique flow.

+
+
+
+
+
+ +
+

Flow Builder

+

Design complex conversation trees with buttons, quick replies, and images. No coding required.

+
+
+
+
+
+ +
+

Real-time Analytics

+

Track bot performance and view chat history in real-time to optimize your customer experience.

+
+
+
+
+
+{% endblock %} diff --git a/core/urls.py b/core/urls.py index 6299e3d..28de9ee 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,7 +1,9 @@ from django.urls import path - -from .views import home +from . import views urlpatterns = [ - path("", home, name="home"), -] + path('', views.home, name='home'), + path('dashboard/', views.dashboard, name='dashboard'), + path('fanpages/', views.fanpage_list, name='fanpage_list'), + path('flows/', views.flow_list, name='flow_list'), +] \ No newline at end of file diff --git a/core/views.py b/core/views.py index c9aed12..65ea9e7 100644 --- a/core/views.py +++ b/core/views.py @@ -1,25 +1,32 @@ -import os -import platform - -from django import get_version as django_version -from django.shortcuts import render -from django.utils import timezone - +from django.shortcuts import render, redirect, get_object_or_404 +from django.contrib.auth.decorators import login_required +from .models import Fanpage, Flow, MessageLog, ChatSession def home(request): - """Render the landing screen with loader and environment details.""" - host_name = request.get_host().lower() - agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" - now = timezone.now() + if request.user.is_authenticated: + return redirect('dashboard') + return render(request, 'core/index.html') +@login_required +def dashboard(request): + fanpages = Fanpage.objects.all() + flows = Flow.objects.all() + recent_logs = MessageLog.objects.order_by('-timestamp')[:10] + context = { - "project_name": "New Style", - "agent_brand": agent_brand, - "django_version": django_version(), - "python_version": platform.python_version(), - "current_time": now, - "host_name": host_name, - "project_description": os.getenv("PROJECT_DESCRIPTION", ""), - "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), + 'fanpage_count': fanpages.count(), + 'flow_count': flows.count(), + 'fanpages': fanpages, + 'recent_logs': recent_logs, } - return render(request, "core/index.html", context) + return render(request, 'core/dashboard.html', context) + +@login_required +def fanpage_list(request): + fanpages = Fanpage.objects.all() + return render(request, 'core/fanpage_list.html', {'fanpages': fanpages}) + +@login_required +def flow_list(request): + flows = Flow.objects.all() + return render(request, 'core/flow_list.html', {'flows': flows}) diff --git a/static/css/custom.css b/static/css/custom.css index 925f6ed..811d7ef 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -1,4 +1,74 @@ -/* Custom styles for the application */ -body { - font-family: system-ui, -apple-system, sans-serif; +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Outfit:wght@500;600;700&display=swap'); + +:root { + --fb-indigo: #2D3250; + --fb-slate: #424769; + --fb-grey: #7077A1; + --fb-coral: #F6B17A; + --fb-offwhite: #F0F0F0; } + +body { + font-family: 'Inter', sans-serif; + background-color: var(--fb-offwhite); + color: var(--fb-indigo); +} + +h1, h2, h3, h4, h5, h6 { + font-family: 'Outfit', sans-serif; +} + +.bg-indigo { background-color: var(--fb-indigo) !important; } +.bg-slate { background-color: var(--fb-slate) !important; } +.text-coral { color: var(--fb-coral) !important; } +.btn-coral { + background-color: var(--fb-coral); + color: var(--fb-indigo); + font-weight: 600; + border: none; + transition: all 0.3s ease; +} +.btn-coral:hover { + background-color: #e5a069; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(246, 177, 122, 0.3); +} + +.navbar { + background-color: rgba(45, 50, 80, 0.95); + backdrop-filter: blur(10px); +} + +.card { + border: none; + border-radius: 16px; + box-shadow: 0 4px 24px rgba(0,0,0,0.05); +} + +.hero-section { + background: linear-gradient(135deg, var(--fb-indigo) 0%, var(--fb-slate) 100%); + color: white; + padding: 100px 0; + position: relative; + overflow: hidden; +} + +.hero-section::after { + content: ''; + position: absolute; + top: -10%; + right: -5%; + width: 400px; + height: 400px; + background: radial-gradient(circle, var(--fb-coral) 0%, transparent 70%); + opacity: 0.1; + border-radius: 50%; +} + +.glass-card { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(12px); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 20px; + padding: 30px; +} \ No newline at end of file diff --git a/staticfiles/css/custom.css b/staticfiles/css/custom.css index 108056f..811d7ef 100644 --- a/staticfiles/css/custom.css +++ b/staticfiles/css/custom.css @@ -1,21 +1,74 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Outfit:wght@500;600;700&display=swap'); :root { - --bg-color-start: #6a11cb; - --bg-color-end: #2575fc; - --text-color: #ffffff; - --card-bg-color: rgba(255, 255, 255, 0.01); - --card-border-color: rgba(255, 255, 255, 0.1); + --fb-indigo: #2D3250; + --fb-slate: #424769; + --fb-grey: #7077A1; + --fb-coral: #F6B17A; + --fb-offwhite: #F0F0F0; } + body { - margin: 0; font-family: 'Inter', sans-serif; - background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end)); - color: var(--text-color); - display: flex; - justify-content: center; - align-items: center; - min-height: 100vh; - text-align: center; - overflow: hidden; - position: relative; + background-color: var(--fb-offwhite); + color: var(--fb-indigo); } + +h1, h2, h3, h4, h5, h6 { + font-family: 'Outfit', sans-serif; +} + +.bg-indigo { background-color: var(--fb-indigo) !important; } +.bg-slate { background-color: var(--fb-slate) !important; } +.text-coral { color: var(--fb-coral) !important; } +.btn-coral { + background-color: var(--fb-coral); + color: var(--fb-indigo); + font-weight: 600; + border: none; + transition: all 0.3s ease; +} +.btn-coral:hover { + background-color: #e5a069; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(246, 177, 122, 0.3); +} + +.navbar { + background-color: rgba(45, 50, 80, 0.95); + backdrop-filter: blur(10px); +} + +.card { + border: none; + border-radius: 16px; + box-shadow: 0 4px 24px rgba(0,0,0,0.05); +} + +.hero-section { + background: linear-gradient(135deg, var(--fb-indigo) 0%, var(--fb-slate) 100%); + color: white; + padding: 100px 0; + position: relative; + overflow: hidden; +} + +.hero-section::after { + content: ''; + position: absolute; + top: -10%; + right: -5%; + width: 400px; + height: 400px; + background: radial-gradient(circle, var(--fb-coral) 0%, transparent 70%); + opacity: 0.1; + border-radius: 50%; +} + +.glass-card { + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(12px); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 20px; + padding: 30px; +} \ No newline at end of file