Continue AI features and improve protocol support

This commit is contained in:
2026-02-15 16:57:32 +00:00
parent 2d3b8fdac6
commit 85e97e895d
62 changed files with 5472 additions and 441 deletions

View File

@@ -1,12 +1,13 @@
import base64
import orjson
import requests
from django.conf import settings
from django.shortcuts import render
from django.urls import reverse
from django.views import View
from mixins.views import ObjectList, ObjectRead
from core.models import Chat
from core.clients import transport
from core.models import Chat, PersonIdentifier
from core.views.manage.permissions import SuperUserRequiredMixin
@@ -18,13 +19,25 @@ class CustomObjectRead(ObjectRead):
class Signal(SuperUserRequiredMixin, View):
template_name = "pages/signal.html"
service = "signal"
page_title = "Signal"
accounts_url_name = "signal_accounts"
def get(self, request):
return render(request, self.template_name)
return render(
request,
self.template_name,
{
"service": self.service,
"service_label": self.page_title,
"accounts_url_name": self.accounts_url_name,
},
)
class SignalAccounts(SuperUserRequiredMixin, ObjectList):
list_template = "partials/signal-accounts.html"
service = "signal"
context_object_name_singular = "Signal Account"
context_object_name = "Signal Accounts"
@@ -32,13 +45,44 @@ class SignalAccounts(SuperUserRequiredMixin, ObjectList):
list_url_name = "signal_accounts"
list_url_args = ["type"]
def get_queryset(self, **kwargs):
# url = signal:8080/v1/accounts
url = f"http://signal:8080/v1/accounts"
response = requests.get(url)
accounts = orjson.loads(response.text)
def _normalize_accounts(self, rows):
out = []
for item in rows or []:
if isinstance(item, dict):
value = (
item.get("number")
or item.get("id")
or item.get("jid")
or item.get("account")
)
if value:
out.append(str(value))
elif item:
out.append(str(item))
return out
return accounts
def _service_context(self, service, label, add_url_name, show_contact_actions):
return {
"service": service,
"service_label": label,
"account_add_url_name": add_url_name,
"show_contact_actions": show_contact_actions,
"endpoint_base": str(
getattr(settings, "SIGNAL_HTTP_URL", "http://signal:8080")
).rstrip("/")
if service == "signal"
else "",
"service_warning": transport.get_service_warning(service),
}
def get_queryset(self, **kwargs):
self.extra_context = self._service_context(
service="signal",
label="Signal",
add_url_name="signal_account_add",
show_contact_actions=True,
)
return self._normalize_accounts(transport.list_accounts("signal"))
class SignalContactsList(SuperUserRequiredMixin, ObjectList):
@@ -55,13 +99,16 @@ class SignalContactsList(SuperUserRequiredMixin, ObjectList):
# /v1/configuration/{number}/settings
# /v1/identities/{number}
# /v1/contacts/{number}
# response = requests.get(f"http://signal:8080/v1/configuration/{self.kwargs['pk']}/settings")
# response = requests.get(
# f"http://signal:8080/v1/configuration/{self.kwargs['pk']}/settings"
# )
# config = orjson.loads(response.text)
response = requests.get(f"http://signal:8080/v1/identities/{self.kwargs['pk']}")
base = getattr(settings, "SIGNAL_HTTP_URL", "http://signal:8080").rstrip("/")
response = requests.get(f"{base}/v1/identities/{self.kwargs['pk']}")
identities = orjson.loads(response.text)
response = requests.get(f"http://signal:8080/v1/contacts/{self.kwargs['pk']}")
response = requests.get(f"{base}/v1/contacts/{self.kwargs['pk']}")
contacts = orjson.loads(response.text)
# add identities to contacts
@@ -90,8 +137,59 @@ class SignalChatsList(SuperUserRequiredMixin, ObjectList):
def get_queryset(self, *args, **kwargs):
pk = self.kwargs.get("pk", "")
object_list = Chat.objects.filter(account=pk)
return object_list
chats = list(Chat.objects.filter(account=pk))
rows = []
for chat in chats:
identifier_candidates = [
str(chat.source_uuid or "").strip(),
str(chat.source_number or "").strip(),
]
identifier_candidates = [value for value in identifier_candidates if value]
person_identifier = None
if identifier_candidates:
person_identifier = (
PersonIdentifier.objects.filter(
user=self.request.user,
service="signal",
identifier__in=identifier_candidates,
)
.select_related("person")
.first()
)
identifier_value = (
person_identifier.identifier if person_identifier else ""
) or (chat.source_uuid or chat.source_number or "")
service = "signal"
compose_page_url = ""
compose_widget_url = ""
if identifier_value:
query = f"service={service}&identifier={identifier_value}"
if person_identifier:
query += f"&person={person_identifier.person_id}"
compose_page_url = f"{reverse('compose_page')}?{query}"
compose_widget_url = f"{reverse('compose_widget')}?{query}"
if person_identifier:
ai_url = (
f"{reverse('ai_workspace')}?person={person_identifier.person_id}"
)
else:
ai_url = reverse("ai_workspace")
rows.append(
{
"chat": chat,
"compose_page_url": compose_page_url,
"compose_widget_url": compose_widget_url,
"ai_url": ai_url,
"person_name": (
person_identifier.person.name if person_identifier else ""
),
"manual_icon_class": "fa-solid fa-paper-plane",
"can_compose": bool(compose_page_url),
}
)
return rows
class SignalMessagesList(SuperUserRequiredMixin, ObjectList):
@@ -100,6 +198,7 @@ class SignalMessagesList(SuperUserRequiredMixin, ObjectList):
class SignalAccountAdd(SuperUserRequiredMixin, CustomObjectRead):
detail_template = "partials/signal-account-add.html"
service = "signal"
context_object_name_singular = "Add Account"
context_object_name = "Add Account"
@@ -112,9 +211,7 @@ class SignalAccountAdd(SuperUserRequiredMixin, CustomObjectRead):
def get_object(self, **kwargs):
form_args = self.request.POST.dict()
device_name = form_args["device"]
url = f"http://signal:8080/v1/qrcodelink?device_name={device_name}"
response = requests.get(url)
image_bytes = response.content
base64_image = base64.b64encode(image_bytes).decode("utf-8")
image_bytes = transport.get_link_qr(self.service, device_name)
base64_image = transport.image_bytes_to_base64(image_bytes)
return base64_image