Production latest code push
This commit is contained in:
commit
2fc5bb1b21
30
.env
Normal file
30
.env
Normal file
@ -0,0 +1,30 @@
|
||||
# Database Configuration
|
||||
POSTGRES_DB=polisplexity
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=mysecretpassword
|
||||
|
||||
# Django Environment Variables
|
||||
DEBUG=True
|
||||
SECRET_KEY=django-insecure-%*=%u3gv38cv*2iwy)m^)flo3p4w7ol*n5*-7lr*i4^u+(v=#q
|
||||
ALLOWED_HOSTS=127.0.0.1,localhost,app.polisplexity.tech,191.101.233.39
|
||||
|
||||
# Database URL
|
||||
DATABASE_URL=postgres://postgres:mysecretpassword@db:5432/polisplexity
|
||||
|
||||
# Static and Media Files
|
||||
STATIC_URL=/static/
|
||||
STATIC_ROOT=/app/static
|
||||
MEDIA_URL=/media/
|
||||
MEDIA_ROOT=/app/media
|
||||
|
||||
# Neo4j Database Configuration
|
||||
NEO4J_URI=neo4j+s://74d433fb.databases.neo4j.io
|
||||
NEO4J_USERNAME=neo4j
|
||||
NEO4J_PASSWORD=4Y5-ppefHkgEiLr-l0qzbf8wNJw0zkOmRmk7cSkSrTg
|
||||
|
||||
# OpenAI API Key
|
||||
OPENAI_API_KEY=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
|
||||
|
||||
# Facebook API Tokens
|
||||
PAGE_ACCESS_TOKEN=EAAIq9z4rVPIBOxJxRnmbjIUsqJ9ZB5hZC9MF4qN64VNpxUCYguMCqUNKSsAjQZAcD9hlhZCv2RcV4GOIFC3Ni6VGoMp3rTFlLwtXxFIklj0FqZAVqSh7i0QT3Kwt9SCx9V9iioSsyFhUQrnpTXZCoDPJy0i2kMkzkY5ZA58hieSeQZBZARz3ZC7XeZCi5uSZBXYCeatGuAZDZD
|
||||
VERIFY_TOKEN=YzQ2VWcODWO922j30HZ9AV113kAisTjcacc3wzURPvFjHCOWjcYP39ThgCWlPQ1w
|
0
.env:Zone.Identifier
Normal file
0
.env:Zone.Identifier
Normal file
17
Dockerfile
Normal file
17
Dockerfile
Normal file
@ -0,0 +1,17 @@
|
||||
# Use official Python image
|
||||
FROM python:3.10
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Install dependencies
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Expose the application port
|
||||
EXPOSE 8000
|
||||
|
||||
# Run Gunicorn server
|
||||
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "polisplexity.wsgi:application"]
|
0
core/__init__.py
Normal file
0
core/__init__.py
Normal file
BIN
core/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
core/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/admin.cpython-310.pyc
Normal file
BIN
core/__pycache__/admin.cpython-310.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/apps.cpython-310.pyc
Normal file
BIN
core/__pycache__/apps.cpython-310.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/models.cpython-310.pyc
Normal file
BIN
core/__pycache__/models.cpython-310.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/urls.cpython-310.pyc
Normal file
BIN
core/__pycache__/urls.cpython-310.pyc
Normal file
Binary file not shown.
BIN
core/__pycache__/views.cpython-310.pyc
Normal file
BIN
core/__pycache__/views.cpython-310.pyc
Normal file
Binary file not shown.
5
core/admin.py
Normal file
5
core/admin.py
Normal file
@ -0,0 +1,5 @@
|
||||
from django.contrib import admin
|
||||
from .models import Category, UserMenu
|
||||
|
||||
admin.site.register(Category)
|
||||
admin.site.register(UserMenu)
|
6
core/apps.py
Normal file
6
core/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CoreConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "core"
|
29
core/migrations/0001_initial.py
Normal file
29
core/migrations/0001_initial.py
Normal file
@ -0,0 +1,29 @@
|
||||
# Generated by Django 5.0.3 on 2024-03-16 20:14
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="Category",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=100)),
|
||||
("description", models.TextField(blank=True, null=True)),
|
||||
],
|
||||
),
|
||||
]
|
51
core/migrations/0002_usermenu.py
Normal file
51
core/migrations/0002_usermenu.py
Normal file
@ -0,0 +1,51 @@
|
||||
# Generated by Django 5.0.3 on 2024-03-16 20:22
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("core", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="UserMenu",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("title", models.CharField(max_length=100)),
|
||||
("order", models.IntegerField(default=0)),
|
||||
("description", models.TextField()),
|
||||
("icon", models.CharField(blank=True, max_length=50, null=True)),
|
||||
(
|
||||
"image",
|
||||
models.ImageField(
|
||||
blank=True, null=True, upload_to="media/images/menu/"
|
||||
),
|
||||
),
|
||||
("url", models.CharField(blank=True, max_length=200, null=True)),
|
||||
("url_text", models.CharField(blank=True, max_length=50, null=True)),
|
||||
(
|
||||
"category",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="user_menus",
|
||||
to="core.category",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"ordering": ["category", "order"],
|
||||
},
|
||||
),
|
||||
]
|
0
core/migrations/__init__.py
Normal file
0
core/migrations/__init__.py
Normal file
BIN
core/migrations/__pycache__/0001_initial.cpython-310.pyc
Normal file
BIN
core/migrations/__pycache__/0001_initial.cpython-310.pyc
Normal file
Binary file not shown.
BIN
core/migrations/__pycache__/0002_usermenu.cpython-310.pyc
Normal file
BIN
core/migrations/__pycache__/0002_usermenu.cpython-310.pyc
Normal file
Binary file not shown.
BIN
core/migrations/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
core/migrations/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
28
core/models.py
Normal file
28
core/models.py
Normal file
@ -0,0 +1,28 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
class Category(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class UserMenu(models.Model):
|
||||
title = models.CharField(max_length=100)
|
||||
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='user_menus') # Assuming each menu belongs to one category
|
||||
order = models.IntegerField(default=0)
|
||||
description = models.TextField()
|
||||
icon = models.CharField(max_length=50, blank=True, null=True)
|
||||
image = models.ImageField(upload_to='media/images/menu/', null=True, blank=True) # Changed 'media/images/' to 'user_menu_images/' for clarity
|
||||
url = models.CharField(max_length=200, blank=True, null=True)
|
||||
url_text = models.CharField(max_length=50, blank=True, null=True)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
class Meta:
|
||||
ordering = ['category', 'order'] # Orders by category first, then by the order field within each category
|
||||
|
||||
|
66
core/templates/core/index.html
Normal file
66
core/templates/core/index.html
Normal file
@ -0,0 +1,66 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Polisplexity Portal{% endblock title %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.card-img-top {
|
||||
height: 200px; /* Fixed height for consistency */
|
||||
object-fit: cover; /* Ensures image covers the area nicely */
|
||||
}
|
||||
.card:hover {
|
||||
transform: scale(1.05); /* Slight zoom effect on hover for interactivity */
|
||||
transition: transform 0.3s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
{% endblock extra_css %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4">
|
||||
<h2 class="text-center mb-4">Welcome to Polisplexity</h2>
|
||||
<p class="text-center mb-4">Select one of the options Bellow</p>
|
||||
|
||||
{% for category, items in grouped_menu_items.items %}
|
||||
{% if not forloop.first %}
|
||||
<hr> <!-- Horizontal separator for every new category except the first one -->
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-3 mb-4">
|
||||
<h3> {{ category.name }}</h3>
|
||||
<small> {{ category.description }}</small>
|
||||
</div>
|
||||
{% for menu_item in items %}
|
||||
<div class="col-12 col-md-3 mb-4">
|
||||
<div class="card h-100">
|
||||
{% if menu_item.image %}
|
||||
<!-- Display Image if available -->
|
||||
<img src="{{ menu_item.image.url }}" class="card-img-top" alt="{{ menu_item.title }}">
|
||||
{% elif menu_item.icon %}
|
||||
<!-- Display Icon if image is not available but icon is -->
|
||||
<div class="card-header text-center">
|
||||
<span class="material-symbols-rounded md-48">{{ menu_item.icon }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{ menu_item.title }}</h5>
|
||||
<p class="card-text">{{ menu_item.description }}</p>
|
||||
</div>
|
||||
<div class="card-footer bg-white">
|
||||
<a href="{{ menu_item.url }}" class="btn btn-primary">{{ menu_item.url_text }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
$(function () {
|
||||
$('[data-bs-toggle="tooltip"]').tooltip() // Initialize Bootstrap tooltips
|
||||
})
|
||||
</script>
|
||||
{% endblock extra_js %}
|
3
core/tests.py
Normal file
3
core/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
6
core/urls.py
Normal file
6
core/urls.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.urls import path
|
||||
from .views import index
|
||||
|
||||
urlpatterns = [
|
||||
path('', index, name='index'),
|
||||
]
|
11
core/views.py
Normal file
11
core/views.py
Normal file
@ -0,0 +1,11 @@
|
||||
from django.shortcuts import render
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from .models import UserMenu
|
||||
from itertools import groupby
|
||||
|
||||
|
||||
def index(request):
|
||||
menu_items = UserMenu.objects.all().order_by('category', 'order')
|
||||
# Group by category
|
||||
grouped_menu_items = {k: list(v) for k, v in groupby(menu_items, lambda x: x.category)}
|
||||
return render(request, 'core/index.html', {'grouped_menu_items': grouped_menu_items})
|
BIN
db.sqlite3
Normal file
BIN
db.sqlite3
Normal file
Binary file not shown.
39
docker-compose.yml
Normal file
39
docker-compose.yml
Normal file
@ -0,0 +1,39 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:15
|
||||
container_name: polisplexity_postgres
|
||||
restart: always
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "5434:5432"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||
interval: 10s
|
||||
retries: 5
|
||||
timeout: 5s
|
||||
|
||||
web:
|
||||
build: .
|
||||
container_name: polisplexity_django
|
||||
restart: always
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- .:/app # Ensure correct project structure
|
||||
ports:
|
||||
- "8010:8001"
|
||||
env_file:
|
||||
- .env
|
||||
command: >
|
||||
sh -c "python manage.py migrate &&
|
||||
python manage.py collectstatic --noinput &&
|
||||
exec gunicorn --bind 0.0.0.0:8001 polisplexity.wsgi:application"
|
||||
|
||||
volumes:
|
||||
pgdata:
|
22
manage.py
Normal file
22
manage.py
Normal file
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "polisplexity.settings")
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
BIN
media/images/logos/logo_U4MX.png
Normal file
BIN
media/images/logos/logo_U4MX.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 44 KiB |
BIN
media/media/images/menu/01_Forms_image.webp
Normal file
BIN
media/media/images/menu/01_Forms_image.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 708 KiB |
11
pgloader.load
Normal file
11
pgloader.load
Normal file
@ -0,0 +1,11 @@
|
||||
LOAD DATABASE
|
||||
FROM sqlite:///app/db.sqlite3
|
||||
INTO postgresql://postgres:mysecretpassword@localhost:5434/polisplexity
|
||||
|
||||
WITH include no drop, create tables, create indexes, reset sequences
|
||||
|
||||
SET work_mem to '16MB', maintenance_work_mem to '512 MB'
|
||||
|
||||
ALTER SCHEMA 'main' RENAME TO 'public'
|
||||
;
|
||||
|
0
polisplexity/__init__.py
Normal file
0
polisplexity/__init__.py
Normal file
BIN
polisplexity/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
polisplexity/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
polisplexity/__pycache__/settings.cpython-310.pyc
Normal file
BIN
polisplexity/__pycache__/settings.cpython-310.pyc
Normal file
Binary file not shown.
BIN
polisplexity/__pycache__/urls.cpython-310.pyc
Normal file
BIN
polisplexity/__pycache__/urls.cpython-310.pyc
Normal file
Binary file not shown.
BIN
polisplexity/__pycache__/wsgi.cpython-310.pyc
Normal file
BIN
polisplexity/__pycache__/wsgi.cpython-310.pyc
Normal file
Binary file not shown.
16
polisplexity/asgi.py
Normal file
16
polisplexity/asgi.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
ASGI config for polisplexity project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "polisplexity.settings")
|
||||
|
||||
application = get_asgi_application()
|
127
polisplexity/settings.py
Normal file
127
polisplexity/settings.py
Normal file
@ -0,0 +1,127 @@
|
||||
"""
|
||||
Django settings for polisplexity project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 5.0.3.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.0/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/5.0/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
import dj_database_url
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
BASE_URL = "https://app.polisplexity.tech"
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = os.getenv("SECRET_KEY")
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = os.getenv("DEBUG") == "True"
|
||||
|
||||
ALLOWED_HOSTS = os.getenv("ALLOWED_HOSTS", "").split(",")
|
||||
|
||||
# Application definition
|
||||
INSTALLED_APPS = [
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"core",
|
||||
"pxy_de",
|
||||
"pxy_cr",
|
||||
"pxy_whatsapp",
|
||||
"pxy_city_digital_twins",
|
||||
"pxy_bots",
|
||||
"pxy_openai",
|
||||
"pxy_meta_pages",
|
||||
"pxy_langchain",
|
||||
"pxy_neo4j",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "polisplexity.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [os.path.join(BASE_DIR, "templates")],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = "polisplexity.wsgi.application"
|
||||
|
||||
# Database Configuration
|
||||
DATABASES = {
|
||||
"default": dj_database_url.config(default=os.getenv("DATABASE_URL"))
|
||||
}
|
||||
|
||||
# Password validation
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
|
||||
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
|
||||
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
|
||||
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
|
||||
]
|
||||
|
||||
# Internationalization
|
||||
LANGUAGE_CODE = "en-us"
|
||||
TIME_ZONE = "UTC"
|
||||
USE_I18N = True
|
||||
USE_TZ = True
|
||||
|
||||
STATIC_URL = "/static/"
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, "static") # Ensure this line is correct
|
||||
|
||||
# Add this if missing
|
||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') # Fixes mixed content issues
|
||||
CSRF_TRUSTED_ORIGINS = ['https://app.polisplexity.tech'] # Allow CSRF over HTTPS
|
||||
|
||||
MEDIA_URL = "/media/"
|
||||
MEDIA_ROOT = os.getenv("MEDIA_ROOT", os.path.join(BASE_DIR, "media"))
|
||||
|
||||
# Default primary key field type
|
||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||
|
||||
# Facebook API Tokens
|
||||
PAGE_ACCESS_TOKEN = os.getenv("PAGE_ACCESS_TOKEN")
|
||||
VERIFY_TOKEN = os.getenv("VERIFY_TOKEN")
|
||||
|
||||
os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
|
||||
|
||||
# Neo4j Database Configuration
|
||||
NEO4J_URI = os.getenv("NEO4J_URI")
|
||||
NEO4J_USERNAME = os.getenv("NEO4J_USERNAME")
|
||||
NEO4J_PASSWORD = os.getenv("NEO4J_PASSWORD")
|
||||
|
||||
# OpenAI API Key
|
||||
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
|
36
polisplexity/urls.py
Normal file
36
polisplexity/urls.py
Normal file
@ -0,0 +1,36 @@
|
||||
"""
|
||||
URL configuration for polisplexity project.
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/5.0/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path, include
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
|
||||
admin.site.site_header = "Polisplexity City Technologies"
|
||||
admin.site.site_title = "Polisplexity Admin Portal"
|
||||
admin.site.index_title = "Welcome to Polisplexity City Technologies Portal"
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin.site.urls),
|
||||
path('', include('core.urls')),
|
||||
path('', include('pxy_city_digital_twins.urls')),
|
||||
path('pxy_whatsapp/', include('pxy_whatsapp.urls')),
|
||||
path('bots/', include('pxy_bots.urls')), # Webhook URL: /bots/webhook/<bot_name>/
|
||||
path('pxy_meta_pages/', include('pxy_meta_pages.urls', namespace='pxy_meta_pages')),
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
16
polisplexity/wsgi.py
Normal file
16
polisplexity/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for polisplexity project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "polisplexity.settings")
|
||||
|
||||
application = get_wsgi_application()
|
2149
polisplexity_dump.sql
Normal file
2149
polisplexity_dump.sql
Normal file
File diff suppressed because it is too large
Load Diff
0
pxy_bots/__init__.py
Normal file
0
pxy_bots/__init__.py
Normal file
BIN
pxy_bots/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
pxy_bots/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pxy_bots/__pycache__/admin.cpython-310.pyc
Normal file
BIN
pxy_bots/__pycache__/admin.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pxy_bots/__pycache__/apps.cpython-310.pyc
Normal file
BIN
pxy_bots/__pycache__/apps.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pxy_bots/__pycache__/handlers.cpython-310.pyc
Normal file
BIN
pxy_bots/__pycache__/handlers.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pxy_bots/__pycache__/models.cpython-310.pyc
Normal file
BIN
pxy_bots/__pycache__/models.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pxy_bots/__pycache__/urls.cpython-310.pyc
Normal file
BIN
pxy_bots/__pycache__/urls.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pxy_bots/__pycache__/utils.cpython-310.pyc
Normal file
BIN
pxy_bots/__pycache__/utils.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pxy_bots/__pycache__/views.cpython-310.pyc
Normal file
BIN
pxy_bots/__pycache__/views.cpython-310.pyc
Normal file
Binary file not shown.
52
pxy_bots/admin.py
Normal file
52
pxy_bots/admin.py
Normal file
@ -0,0 +1,52 @@
|
||||
from django.contrib import admin
|
||||
from .models import TelegramBot
|
||||
from django.utils.html import format_html
|
||||
|
||||
@admin.register(TelegramBot)
|
||||
class TelegramBotAdmin(admin.ModelAdmin):
|
||||
list_display = ("name", "username", "is_active", "get_assistant_name", "set_webhook_action")
|
||||
search_fields = ("name", "username")
|
||||
list_filter = ("is_active",)
|
||||
actions = ["set_webhooks"]
|
||||
|
||||
@admin.action(description="Set webhooks for selected bots")
|
||||
def set_webhooks(self, request, queryset):
|
||||
base_url = request.build_absolute_uri("/")[:-1] # Get base server URL
|
||||
results = []
|
||||
for bot in queryset:
|
||||
if bot.is_active:
|
||||
try:
|
||||
result = bot.set_webhook(base_url)
|
||||
self.message_user(
|
||||
request,
|
||||
f"Webhook set for {bot.name}: {result}",
|
||||
level="success",
|
||||
)
|
||||
except Exception as e:
|
||||
self.message_user(
|
||||
request,
|
||||
f"Failed to set webhook for {bot.name}: {str(e)}",
|
||||
level="error",
|
||||
)
|
||||
results.append(result)
|
||||
else:
|
||||
self.message_user(
|
||||
request,
|
||||
f"Skipped inactive bot: {bot.name}",
|
||||
level="warning",
|
||||
)
|
||||
|
||||
def get_assistant_name(self, obj):
|
||||
"""Show the name of the assistant linked to the bot."""
|
||||
return obj.assistant.name if obj.assistant else "None"
|
||||
|
||||
get_assistant_name.short_description = "Assistant Name"
|
||||
|
||||
def set_webhook_action(self, obj):
|
||||
"""Button in the Django admin to manually trigger webhook setup."""
|
||||
return format_html(
|
||||
'<a class="button" href="{}">Set Webhook</a>',
|
||||
f"/admin/pxy_bots/set_webhook/{obj.id}/"
|
||||
)
|
||||
|
||||
set_webhook_action.short_description = "Webhook"
|
6
pxy_bots/apps.py
Normal file
6
pxy_bots/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PxyBotsConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "pxy_bots"
|
73
pxy_bots/handlers.py
Normal file
73
pxy_bots/handlers.py
Normal file
@ -0,0 +1,73 @@
|
||||
from telegram import Update, ForceReply
|
||||
import logging
|
||||
from pxy_openai.assistants import OpenAIAssistant
|
||||
from .models import TelegramBot
|
||||
from asgiref.sync import sync_to_async
|
||||
from pxy_langchain.models import AIAssistant
|
||||
from pxy_langchain.services import LangchainAIService
|
||||
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
async def dream_city_command(update: Update):
|
||||
"""Send a message with a link to the random city generator."""
|
||||
message = (
|
||||
"Descubre la ciudad de tus sueños! Haz clic en el enlace para explorar una ciudad generada aleatoriamente que podría reflejar tus aspiraciones urbanas: "
|
||||
"https://app.polisplexity.tech/city/digital/twin/dream/?innovation=30&technology=30&science=40"
|
||||
)
|
||||
await update.message.reply_text(message)
|
||||
|
||||
|
||||
async def start(update: Update):
|
||||
"""Send a message when the command /start is issued."""
|
||||
user = update.effective_user
|
||||
await update.message.reply_html(
|
||||
rf"Hi {user.mention_html()}!",
|
||||
reply_markup=ForceReply(selective=True),
|
||||
)
|
||||
|
||||
|
||||
async def help_command(update: Update):
|
||||
"""Send a message when the command /help is issued."""
|
||||
user = update.effective_user
|
||||
await update.message.reply_text(f"Help! How can I assist you, {user.first_name}?")
|
||||
|
||||
|
||||
async def handle_location(update: Update):
|
||||
"""Respond to a location message."""
|
||||
location = update.message.location
|
||||
if location:
|
||||
await update.message.reply_text(
|
||||
f"Thanks for sharing your location! Latitude: {location.latitude}, Longitude: {location.longitude}"
|
||||
)
|
||||
else:
|
||||
await update.message.reply_text("Please share your location.")
|
||||
|
||||
|
||||
async def respond(update, bot_name):
|
||||
"""Respond to user messages using the LangChain AI service."""
|
||||
try:
|
||||
user_message = update.message.text
|
||||
|
||||
# Fetch the Telegram bot and its assigned AI assistant asynchronously
|
||||
telegram_bot = await sync_to_async(TelegramBot.objects.get)(name=bot_name, is_active=True)
|
||||
|
||||
if not telegram_bot.assistant:
|
||||
raise ValueError(f"No assistant configured for bot '{bot_name}'.")
|
||||
|
||||
# Fetch the AI assistant linked to this bot
|
||||
assistant = await sync_to_async(AIAssistant.objects.get)(name=telegram_bot.assistant.name)
|
||||
|
||||
# Initialize AI service
|
||||
ai_service = LangchainAIService(assistant)
|
||||
|
||||
# Generate response asynchronously
|
||||
bot_response = await sync_to_async(ai_service.generate_response)(user_message)
|
||||
|
||||
await update.message.reply_text(bot_response)
|
||||
|
||||
except TelegramBot.DoesNotExist:
|
||||
await update.message.reply_text(f"Bot '{bot_name}' not found or inactive.")
|
||||
except Exception as e:
|
||||
await update.message.reply_text(f"Oops! Something went wrong: {e}")
|
24
pxy_bots/migrations/0001_initial.py
Normal file
24
pxy_bots/migrations/0001_initial.py
Normal file
@ -0,0 +1,24 @@
|
||||
# Generated by Django 4.0.6 on 2025-01-16 12:39
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TelegramBot',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text="Name of the bot (e.g., 'SupportBot').", max_length=50, unique=True)),
|
||||
('username', models.CharField(help_text="The bot's Telegram username (e.g., 'SupportBot').", max_length=50, unique=True)),
|
||||
('token', models.CharField(help_text='Telegram Bot API token.', max_length=200, unique=True)),
|
||||
('is_active', models.BooleanField(default=True, help_text='Is this bot active?')),
|
||||
],
|
||||
),
|
||||
]
|
@ -0,0 +1,43 @@
|
||||
# Generated by Django 5.0.3 on 2025-01-16 19:49
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("pxy_bots", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="telegrambot",
|
||||
name="is_active",
|
||||
field=models.BooleanField(
|
||||
default=True, help_text="Indicates if this bot is active."
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="telegrambot",
|
||||
name="name",
|
||||
field=models.CharField(
|
||||
help_text="Bot name (e.g., 'SupportBot').", max_length=50, unique=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="telegrambot",
|
||||
name="token",
|
||||
field=models.CharField(
|
||||
help_text="Telegram bot token.", max_length=200, unique=True
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="telegrambot",
|
||||
name="username",
|
||||
field=models.CharField(
|
||||
help_text="Bot username (e.g., 'SupportBot').",
|
||||
max_length=50,
|
||||
unique=True,
|
||||
),
|
||||
),
|
||||
]
|
30
pxy_bots/migrations/0003_telegrambot_assistant.py
Normal file
30
pxy_bots/migrations/0003_telegrambot_assistant.py
Normal file
@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.0.3 on 2025-01-28 21:47
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
(
|
||||
"pxy_bots",
|
||||
"0002_alter_telegrambot_is_active_alter_telegrambot_name_and_more",
|
||||
),
|
||||
("pxy_openai", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="telegrambot",
|
||||
name="assistant",
|
||||
field=models.ForeignKey(
|
||||
default=1,
|
||||
help_text="The OpenAI assistant associated with this Telegram bot.",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="telegram_bots",
|
||||
to="pxy_openai.openaiassistant",
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
@ -0,0 +1,38 @@
|
||||
# Generated by Django 5.0.3 on 2025-01-29 02:08
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("contenttypes", "0002_remove_content_type_name"),
|
||||
("pxy_bots", "0003_telegrambot_assistant"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="telegrambot",
|
||||
name="assistant",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="telegrambot",
|
||||
name="assistant_id",
|
||||
field=models.PositiveIntegerField(
|
||||
default=1, help_text="The ID of the associated assistant."
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="telegrambot",
|
||||
name="assistant_type",
|
||||
field=models.ForeignKey(
|
||||
default=1,
|
||||
help_text="The assistant model type.",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="contenttypes.contenttype",
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
@ -0,0 +1,38 @@
|
||||
# Generated by Django 5.0.3 on 2025-01-29 03:08
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("pxy_bots", "0004_remove_telegrambot_assistant_and_more"),
|
||||
(
|
||||
"pxy_langchain",
|
||||
"0002_remove_aiprovider_base_url_aiassistant_created_at_and_more",
|
||||
),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="telegrambot",
|
||||
name="assistant_id",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="telegrambot",
|
||||
name="assistant_type",
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="telegrambot",
|
||||
name="assistant",
|
||||
field=models.ForeignKey(
|
||||
default=1,
|
||||
help_text="The LangChain AI assistant associated with this Telegram bot.",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="telegram_bots",
|
||||
to="pxy_langchain.aiassistant",
|
||||
),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
0
pxy_bots/migrations/__init__.py
Normal file
0
pxy_bots/migrations/__init__.py
Normal file
BIN
pxy_bots/migrations/__pycache__/0001_initial.cpython-310.pyc
Normal file
BIN
pxy_bots/migrations/__pycache__/0001_initial.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
pxy_bots/migrations/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
pxy_bots/migrations/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
47
pxy_bots/models.py
Normal file
47
pxy_bots/models.py
Normal file
@ -0,0 +1,47 @@
|
||||
from django.db import models
|
||||
from pxy_langchain.models import AIAssistant # Now referencing LangChain AI assistants
|
||||
|
||||
class TelegramBot(models.Model):
|
||||
"""
|
||||
Represents a Telegram bot that interacts with users using a LangChain AI assistant.
|
||||
"""
|
||||
name = models.CharField(max_length=50, unique=True, help_text="Bot name (e.g., 'SupportBot').")
|
||||
username = models.CharField(max_length=50, unique=True, help_text="Bot username (e.g., 'SupportBot').")
|
||||
token = models.CharField(max_length=200, unique=True, help_text="Telegram bot token.")
|
||||
is_active = models.BooleanField(default=True, help_text="Indicates if this bot is active.")
|
||||
assistant = models.ForeignKey(
|
||||
AIAssistant,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="telegram_bots",
|
||||
help_text="The LangChain AI assistant associated with this Telegram bot.",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} (@{self.username})"
|
||||
|
||||
@staticmethod
|
||||
def get_bot_token(bot_name):
|
||||
"""Retrieve the token for the given bot name."""
|
||||
try:
|
||||
bot = TelegramBot.objects.get(name=bot_name, is_active=True)
|
||||
return bot.token
|
||||
except TelegramBot.DoesNotExist:
|
||||
raise ValueError(f"Bot with name '{bot_name}' not found or inactive.")
|
||||
|
||||
def set_webhook(self, base_url):
|
||||
"""
|
||||
Set the webhook for this bot dynamically based on the server's base URL.
|
||||
"""
|
||||
if not self.is_active:
|
||||
raise ValueError(f"Bot '{self.name}' is inactive. Activate it before setting the webhook.")
|
||||
|
||||
webhook_url = f"{base_url}/bots/webhook/{self.name}/"
|
||||
response = requests.post(
|
||||
f"https://api.telegram.org/bot{self.token}/setWebhook",
|
||||
data={"url": webhook_url}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
return response.json()
|
||||
else:
|
||||
raise ValueError(f"Failed to set webhook for {self.name}: {response.json()}")
|
16
pxy_bots/set_webhook.py
Normal file
16
pxy_bots/set_webhook.py
Normal file
@ -0,0 +1,16 @@
|
||||
import requests
|
||||
from pxy_bots.models import TelegramBot
|
||||
|
||||
BASE_URL = "https://your-domain.com/bots/webhook/"
|
||||
|
||||
def set_telegram_webhooks():
|
||||
"""Sets webhooks for all active bots."""
|
||||
bots = TelegramBot.objects.filter(is_active=True)
|
||||
for bot in bots:
|
||||
webhook_url = f"{BASE_URL}{bot.name}/"
|
||||
response = requests.post(
|
||||
f"https://api.telegram.org/bot{bot.token}/setWebhook",
|
||||
data={"url": webhook_url}
|
||||
)
|
||||
print(f"Webhook for {bot.name} ({bot.username}) set to: {webhook_url}")
|
||||
print("Response:", response.json())
|
209
pxy_bots/telgram_bot.py
Normal file
209
pxy_bots/telgram_bot.py
Normal file
@ -0,0 +1,209 @@
|
||||
import logging
|
||||
from openai import OpenAI
|
||||
from telegram import ForceReply, Update
|
||||
from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters
|
||||
import json
|
||||
import random
|
||||
|
||||
# Enable logging
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
async def dream_city_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""Send a message with a link to the random city generator."""
|
||||
# Construct the message
|
||||
message = (
|
||||
"Descubre la ciudad de tus sueños! Haz clic en el enlace para explorar una ciudad generada aleatoriamente que podría reflejar tus aspiraciones urbanas: "
|
||||
"https://app.polisplexity.tech/city/digital/twin/dream/?innovation=30&technology=30&science=40"
|
||||
)
|
||||
# Send the message
|
||||
await update.message.reply_text(message)
|
||||
|
||||
|
||||
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""Send a message when the command /start is issued."""
|
||||
user = update.effective_user
|
||||
await update.message.reply_html(
|
||||
rf"Hi {user.mention_html()}!",
|
||||
reply_markup=ForceReply(selective=True),
|
||||
)
|
||||
|
||||
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""Send a message when the command /help is issued."""
|
||||
user = update.effective_user
|
||||
location = update.message.location
|
||||
await update.message.reply_text(rf"Help! {user.mention_html()} in {location}")
|
||||
|
||||
async def handle_location(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""Respond to a location message."""
|
||||
location = update.message.location
|
||||
|
||||
if location:
|
||||
# Extract latitude and longitude
|
||||
latitude = location.latitude
|
||||
longitude = location.longitude
|
||||
|
||||
# You can now use the latitude and longitude for your bot's purposes
|
||||
await update.message.reply_text(f"Thanks for sharing your location! Latitude: {latitude}, Longitude: {longitude}")
|
||||
else:
|
||||
# Respond if no location is found in the message
|
||||
await update.message.reply_text("Please share your location.")
|
||||
|
||||
|
||||
|
||||
async def assistant(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""Respond to user message using OpenAI."""
|
||||
bot_response = "Thinking..."
|
||||
try:
|
||||
user_message = update.message.text
|
||||
client = OpenAI(api_key='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',)
|
||||
|
||||
|
||||
neurolitiks_assistant = client.beta.assistants.retrieve("asst_LcyxtrwgxgdHoVwSsuTUf5Ec")
|
||||
|
||||
my_thread = client.beta.threads.create()
|
||||
|
||||
my_thread_message = client.beta.threads.messages.create(
|
||||
thread_id=my_thread.id,
|
||||
role="user",
|
||||
content=user_message,
|
||||
)
|
||||
|
||||
my_run = client.beta.threads.runs.create(
|
||||
thread_id=my_thread.id,
|
||||
assistant_id=neurolitiks_assistant.id,
|
||||
)
|
||||
|
||||
|
||||
# Step 6: Periodically retrieve the Run to check on its status to see if it has moved to completed
|
||||
while my_run.status in ["queued", "in_progress"]:
|
||||
keep_retrieving_run = client.beta.threads.runs.retrieve(
|
||||
thread_id=my_thread.id,
|
||||
run_id=my_run.id
|
||||
)
|
||||
|
||||
print(keep_retrieving_run)
|
||||
|
||||
if keep_retrieving_run.status == "completed":
|
||||
|
||||
# Step 7: Retrieve the Messages added by the Assistant to the Thread
|
||||
all_messages = client.beta.threads.messages.list(
|
||||
thread_id=my_thread.id
|
||||
)
|
||||
bot_response = all_messages.data[0].content[0].text.value #openai_response.choices[0].message.content
|
||||
break
|
||||
elif keep_retrieving_run.status == "queued" or keep_retrieving_run.status == "in_progress":
|
||||
# bot_response += ' ' + my_run.status
|
||||
pass
|
||||
else:
|
||||
bot_response += ' ' + keep_retrieving_run.status
|
||||
break
|
||||
|
||||
if bot_response:
|
||||
await update.message.reply_text(bot_response)
|
||||
else:
|
||||
# In case the response is empty or only whitespace
|
||||
await update.message.reply_text("I'm not sure how to respond to that. 🤔")
|
||||
except Exception as e:
|
||||
logger.error(f"Error while processing the message: {e}")
|
||||
# Send a default response in case of an error
|
||||
await update.message.reply_text(f"Oops, I encountered an issue. Please try again later. 😓 {e}")
|
||||
|
||||
def prepare_persona_feedback(user_query):
|
||||
# Example function to fetch and format feedback based on user query
|
||||
# This could query a database or API for more dynamic data
|
||||
persona_feedback = {
|
||||
"Other Citizens": "Will analyze and propose community driven improvements.",
|
||||
"Technology Makers": "Will analyze integration with technologies, and will say what technologies and how.",
|
||||
"Scientific Innovators": "Will analyze from the urbanist persepective first, and then will recommen what scientific or urban models to use"
|
||||
}
|
||||
return persona_feedback
|
||||
|
||||
|
||||
def generate_system_prompt(persona_feedback):
|
||||
return (f"Based on the urban improvement proposal, seek feedback from various perspectives: "
|
||||
f"Other Citizens suggest: {persona_feedback['Other Citizens']}; "
|
||||
f"Technology Makers recommend: {persona_feedback['Technology Makers']}; "
|
||||
f"Scientific Innovators advise: {persona_feedback['Scientific Innovators']}. "
|
||||
"Evaluate and compile these suggestions to enhance the proposal.")
|
||||
|
||||
|
||||
async def respond(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""Respond to user message using OpenAI."""
|
||||
bot_response = "Thinking..."
|
||||
try:
|
||||
user_query = update.message.text
|
||||
client = OpenAI(api_key='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')
|
||||
|
||||
# Assuming prepare_persona_feedback and generate_system_prompt are defined elsewhere
|
||||
persona_feedback = prepare_persona_feedback(user_query)
|
||||
system_prompt = generate_system_prompt(persona_feedback)
|
||||
|
||||
openai_response = client.chat.completions.create(
|
||||
model="gpt-3.5-turbo-0125",
|
||||
messages=[
|
||||
{"role": "system", "content": "Act as a citizen technology advisor. You will receive enquiries from citizens and will advise on what technologies they could use to resolve them."},
|
||||
{"role": "user", "content": user_query}
|
||||
],
|
||||
)
|
||||
|
||||
bot_response = openai_response.choices[0].message.content
|
||||
|
||||
# Send the main response
|
||||
if bot_response:
|
||||
await update.message.reply_text(bot_response)
|
||||
else:
|
||||
await update.message.reply_text("I'm not sure how to respond to that. 🤔")
|
||||
|
||||
# Generate random percentages for the URL
|
||||
innovation = random.randint(1, 100)
|
||||
technology = random.randint(1, 100)
|
||||
science = random.randint(1, 100)
|
||||
|
||||
# Prepare the promotional message
|
||||
dream_city_link = (
|
||||
f"Descubre la ciudad de tus sueños! Haz clic en el enlace para explorar una ciudad matemática generada en VR "
|
||||
f"que podría reflejar tus aspiraciones urbanas: https://app.polisplexity.tech/city/digital/twin/dream/?"
|
||||
f"innovation={innovation}&technology={technology}&science={science}"
|
||||
)
|
||||
|
||||
# Send the promotional message in a separate follow-up message
|
||||
await update.message.reply_text(dream_city_link)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error while processing the message: {e}")
|
||||
await update.message.reply_text("Oops, I encountered an issue. Please try again later. 😓")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Start the bot."""
|
||||
application = Application.builder().token("6474402815:AAHCSXM7VKwyj5-lVh8p3365eRQ5Nj94H4I").build()
|
||||
|
||||
application.add_handler(CommandHandler("start", start))
|
||||
application.add_handler(CommandHandler("help", help_command))
|
||||
application.add_handler(CommandHandler("dream_city", dream_city_command))
|
||||
|
||||
debate_filter_private = filters.ChatType.PRIVATE & filters.Regex(r'\bmi ciudad de los sueños\b.*:\s*')
|
||||
application.add_handler(MessageHandler(debate_filter_private, dream_city_command))
|
||||
application.add_handler(MessageHandler(filters.ChatType.PRIVATE, respond))
|
||||
|
||||
# Replace 'your_bot_username' with your bot's username without '@'
|
||||
bot_username = 'PolisplexityBot'
|
||||
mention_pattern = fr'@{bot_username}\b'
|
||||
|
||||
# Use the regex filter to check for mentions of the bot
|
||||
mention_filter = filters.Regex(mention_pattern)
|
||||
|
||||
debate_filter_groups = filters.ChatType.GROUPS & filters.Regex(r'\bmi ciudad de los sueños\b.*:\s*')
|
||||
application.add_handler(MessageHandler(mention_filter & debate_filter_groups, dream_city_command))
|
||||
application.add_handler(MessageHandler(mention_filter & filters.ChatType.GROUPS, respond))
|
||||
|
||||
# application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, respond))
|
||||
application.add_handler(MessageHandler(filters.LOCATION, handle_location))
|
||||
|
||||
application.run_polling(allowed_updates=Update.ALL_TYPES)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
3
pxy_bots/tests.py
Normal file
3
pxy_bots/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
6
pxy_bots/urls.py
Normal file
6
pxy_bots/urls.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.urls import path
|
||||
from .views import telegram_webhook
|
||||
|
||||
urlpatterns = [
|
||||
path('webhook/<str:bot_name>/', telegram_webhook, name='telegram_webhook'),
|
||||
]
|
20
pxy_bots/utils.py
Normal file
20
pxy_bots/utils.py
Normal file
@ -0,0 +1,20 @@
|
||||
from .models import TelegramBot
|
||||
|
||||
def get_bot_token(bot_name: str) -> str:
|
||||
"""
|
||||
Retrieve the Telegram bot token by bot name.
|
||||
|
||||
Args:
|
||||
bot_name (str): The name of the bot to fetch.
|
||||
|
||||
Returns:
|
||||
str: The Telegram bot token.
|
||||
|
||||
Raises:
|
||||
ValueError: If no active bot with the given name is found.
|
||||
"""
|
||||
try:
|
||||
bot = TelegramBot.objects.get(name=bot_name, is_active=True)
|
||||
return bot.token
|
||||
except TelegramBot.DoesNotExist:
|
||||
raise ValueError(f"No active bot found with the name '{bot_name}'")
|
69
pxy_bots/views.py
Normal file
69
pxy_bots/views.py
Normal file
@ -0,0 +1,69 @@
|
||||
import json
|
||||
from telegram import Update, Bot
|
||||
from django.http import JsonResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from asgiref.sync import sync_to_async
|
||||
from .models import TelegramBot
|
||||
from pxy_langchain.services import LangchainAIService
|
||||
from .handlers import dream_city_command, start, help_command, handle_location
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@csrf_exempt
|
||||
async def telegram_webhook(request, bot_name):
|
||||
"""
|
||||
Webhook view that handles Telegram updates asynchronously and only uses LangChain.
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Webhook called for bot: {bot_name}")
|
||||
|
||||
# Step 1: Fetch the bot instance asynchronously
|
||||
try:
|
||||
bot_instance = await sync_to_async(TelegramBot.objects.get)(name=bot_name, is_active=True)
|
||||
logger.info(f"Loaded bot configuration: {bot_instance}")
|
||||
except TelegramBot.DoesNotExist:
|
||||
logger.error(f"Bot '{bot_name}' not found or inactive.")
|
||||
return JsonResponse({"error": f"Bot '{bot_name}' not found."}, status=400)
|
||||
|
||||
# Step 2: Ensure the bot has a LangChain assistant
|
||||
if not bot_instance.assistant:
|
||||
logger.error(f"No assistant configured for bot '{bot_name}'.")
|
||||
return JsonResponse({"error": "Assistant not configured."}, status=400)
|
||||
|
||||
# Step 3: Process POST request from Telegram
|
||||
if request.method == "POST":
|
||||
try:
|
||||
request_body = json.loads(request.body.decode("utf-8"))
|
||||
update = Update.de_json(request_body, Bot(token=bot_instance.token))
|
||||
logger.info(f"Update received: {update}")
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error(f"Failed to decode JSON: {e}")
|
||||
return JsonResponse({"error": "Invalid JSON payload"}, status=400)
|
||||
|
||||
# Step 4: Route commands to the appropriate handlers
|
||||
if update.message:
|
||||
if update.message.text == "/start":
|
||||
await start(update)
|
||||
elif update.message.text == "/help":
|
||||
await help_command(update)
|
||||
elif update.message.text == "/dream_city":
|
||||
await dream_city_command(update)
|
||||
elif update.message.location:
|
||||
await handle_location(update)
|
||||
else:
|
||||
# Step 5: Process AI-generated response using LangChain
|
||||
assistant_instance = await sync_to_async(LangchainAIService)(bot_instance.assistant)
|
||||
bot_response = await sync_to_async(assistant_instance.generate_response)(update.message.text)
|
||||
|
||||
# Step 6: Send the response back to Telegram
|
||||
await update.message.reply_text(bot_response)
|
||||
|
||||
return JsonResponse({"status": "ok"})
|
||||
|
||||
logger.warning("Received non-POST request")
|
||||
return JsonResponse({"error": "Invalid request method"}, status=400)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in webhook: {e}")
|
||||
return JsonResponse({"error": f"Unexpected error: {str(e)}"}, status=500)
|
1
pxy_city_digital_twins
Submodule
1
pxy_city_digital_twins
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit bf4767d280cb31a73b62ebb6ff912c604423de52
|
0
pxy_cr/__init__.py
Normal file
0
pxy_cr/__init__.py
Normal file
BIN
pxy_cr/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
pxy_cr/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pxy_cr/__pycache__/admin.cpython-310.pyc
Normal file
BIN
pxy_cr/__pycache__/admin.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pxy_cr/__pycache__/apps.cpython-310.pyc
Normal file
BIN
pxy_cr/__pycache__/apps.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pxy_cr/__pycache__/models.cpython-310.pyc
Normal file
BIN
pxy_cr/__pycache__/models.cpython-310.pyc
Normal file
Binary file not shown.
3
pxy_cr/admin.py
Normal file
3
pxy_cr/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
6
pxy_cr/apps.py
Normal file
6
pxy_cr/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PxyCrConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "pxy_cr"
|
0
pxy_cr/migrations/__init__.py
Normal file
0
pxy_cr/migrations/__init__.py
Normal file
BIN
pxy_cr/migrations/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
pxy_cr/migrations/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
3
pxy_cr/models.py
Normal file
3
pxy_cr/models.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
3
pxy_cr/tests.py
Normal file
3
pxy_cr/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
3
pxy_cr/views.py
Normal file
3
pxy_cr/views.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
0
pxy_de/__init__.py
Normal file
0
pxy_de/__init__.py
Normal file
BIN
pxy_de/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
pxy_de/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pxy_de/__pycache__/admin.cpython-310.pyc
Normal file
BIN
pxy_de/__pycache__/admin.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pxy_de/__pycache__/apps.cpython-310.pyc
Normal file
BIN
pxy_de/__pycache__/apps.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pxy_de/__pycache__/models.cpython-310.pyc
Normal file
BIN
pxy_de/__pycache__/models.cpython-310.pyc
Normal file
Binary file not shown.
36
pxy_de/admin.py
Normal file
36
pxy_de/admin.py
Normal file
@ -0,0 +1,36 @@
|
||||
from django.contrib import admin
|
||||
from .models import PipelineStep, PipelineRun, PipelineProductRun
|
||||
|
||||
class PipelineStepAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'description') # Specify the fields to display
|
||||
|
||||
class PipelineProductRunAdmin(admin.ModelAdmin):
|
||||
list_display = ('pipeline_run', 'step', 'status', 'date')
|
||||
list_filter = ('status', 'step') # Add filtering by status and step
|
||||
search_fields = ('pipeline_run__run_identifier', 'step__name') # Enable search by pipeline_run identifier and step name
|
||||
|
||||
# Register your models with custom ModelAdmin
|
||||
admin.site.register(PipelineStep, PipelineStepAdmin)
|
||||
admin.site.register(PipelineProductRun, PipelineProductRunAdmin)
|
||||
|
||||
# pxy_de/admin.py (or wherever your PipelineRun admin is)
|
||||
from django.contrib import messages
|
||||
from pxy_de.pipelines.pxy_products.run_products import run_pipeline
|
||||
|
||||
def run_pipeline_action(modeladmin, request, queryset):
|
||||
try:
|
||||
run_pipeline()
|
||||
messages.success(request, "Pipeline executed successfully.")
|
||||
except Exception as e:
|
||||
messages.error(request, f"Pipeline failed to execute: {str(e)}")
|
||||
|
||||
run_pipeline_action.short_description = "Run Products Pipeline"
|
||||
|
||||
class PipelineRunAdmin(admin.ModelAdmin):
|
||||
list_display = ['run_identifier', 'overall_status', 'date']
|
||||
ordering = ['-date']
|
||||
actions = [run_pipeline_action]
|
||||
|
||||
admin.site.register(PipelineRun, PipelineRunAdmin)
|
||||
|
||||
|
6
pxy_de/apps.py
Normal file
6
pxy_de/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PxyDeConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "pxy_de"
|
85
pxy_de/migrations/0001_initial.py
Normal file
85
pxy_de/migrations/0001_initial.py
Normal file
@ -0,0 +1,85 @@
|
||||
# Generated by Django 5.0.3 on 2024-03-16 23:56
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = []
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="PipelineRun",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
(
|
||||
"run_identifier",
|
||||
models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
|
||||
),
|
||||
(
|
||||
"overall_status",
|
||||
models.CharField(blank=True, max_length=20, null=True),
|
||||
),
|
||||
("date", models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="PipelineStep",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("name", models.CharField(max_length=100)),
|
||||
("description", models.TextField(blank=True, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name="PipelineProductRun",
|
||||
fields=[
|
||||
(
|
||||
"id",
|
||||
models.BigAutoField(
|
||||
auto_created=True,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
verbose_name="ID",
|
||||
),
|
||||
),
|
||||
("status", models.CharField(max_length=10)),
|
||||
("date", models.DateTimeField(auto_now_add=True)),
|
||||
(
|
||||
"pipeline_run",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="product_runs",
|
||||
to="pxy_de.pipelinerun",
|
||||
),
|
||||
),
|
||||
(
|
||||
"step",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="pxy_de.pipelinestep",
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
0
pxy_de/migrations/__init__.py
Normal file
0
pxy_de/migrations/__init__.py
Normal file
BIN
pxy_de/migrations/__pycache__/0001_initial.cpython-310.pyc
Normal file
BIN
pxy_de/migrations/__pycache__/0001_initial.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pxy_de/migrations/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
pxy_de/migrations/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
30
pxy_de/models.py
Normal file
30
pxy_de/models.py
Normal file
@ -0,0 +1,30 @@
|
||||
# pxy_de/pipelines/models.py
|
||||
import uuid
|
||||
from django.db import models
|
||||
|
||||
class PipelineStep(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class PipelineRun(models.Model):
|
||||
run_identifier = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
|
||||
overall_status = models.CharField(max_length=20, blank=True, null=True)
|
||||
date = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.run_identifier)
|
||||
|
||||
class PipelineProductRun(models.Model):
|
||||
pipeline_run = models.ForeignKey(PipelineRun, on_delete=models.CASCADE, related_name='product_runs')
|
||||
step = models.ForeignKey(PipelineStep, on_delete=models.CASCADE)
|
||||
status = models.CharField(max_length=10)
|
||||
date = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.pipeline_run.run_identifier} - {self.step.name}"
|
||||
|
||||
|
||||
|
0
pxy_de/pipelines/__init__.py
Normal file
0
pxy_de/pipelines/__init__.py
Normal file
BIN
pxy_de/pipelines/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
pxy_de/pipelines/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
pxy_de/pipelines/__pycache__/models.cpython-310.pyc
Normal file
BIN
pxy_de/pipelines/__pycache__/models.cpython-310.pyc
Normal file
Binary file not shown.
0
pxy_de/pipelines/pxy_products/__init__.py
Normal file
0
pxy_de/pipelines/pxy_products/__init__.py
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user