Implement attachment view

This commit is contained in:
2026-02-15 18:58:58 +00:00
parent e7aac36ef9
commit 4cf75b9923
8 changed files with 914 additions and 69 deletions

View File

@@ -8,7 +8,13 @@ from asgiref.sync import sync_to_async
from django.core import signing
from core.models import ChatSession, Message, PersonIdentifier
from core.views.compose import COMPOSE_WS_TOKEN_SALT
from core.realtime.typing_state import get_person_typing_state
from core.views.compose import (
COMPOSE_WS_TOKEN_SALT,
_image_urls_from_text,
_is_url_only_text,
_looks_like_image_url,
)
def _safe_int(value, default=0):
@@ -28,11 +34,24 @@ def _fmt_ts(ts_value):
def _serialize_message(msg):
author = str(msg.custom_author or "").strip()
text_value = str(msg.text or "")
image_urls = _image_urls_from_text(text_value)
image_url = image_urls[0] if image_urls else ""
hide_text = bool(
image_urls
and _is_url_only_text(text_value)
and all(_looks_like_image_url(url) for url in image_urls)
)
display_text = text_value if text_value.strip() else ("(no text)" if not image_url else "")
return {
"id": str(msg.id),
"ts": int(msg.ts or 0),
"display_ts": _fmt_ts(msg.ts),
"text": str(msg.text or ""),
"text": text_value,
"display_text": display_text,
"image_url": image_url,
"image_urls": image_urls,
"hide_text": hide_text,
"author": author,
"outgoing": author.upper() in {"USER", "BOT"},
}
@@ -59,14 +78,18 @@ def _load_since(user_id, service, identifier, person_id, after_ts, limit):
identifier=identifier,
).first()
if person_identifier is None:
return {"messages": [], "last_ts": after_ts}
return {"messages": [], "last_ts": after_ts, "person_id": 0}
session = ChatSession.objects.filter(
user_id=user_id,
identifier=person_identifier,
).first()
if session is None:
return {"messages": [], "last_ts": after_ts}
return {
"messages": [],
"last_ts": after_ts,
"person_id": int(person_identifier.person_id),
}
qs = Message.objects.filter(
user_id=user_id,
@@ -85,6 +108,7 @@ def _load_since(user_id, service, identifier, person_id, after_ts, limit):
return {
"messages": [_serialize_message(row) for row in rows],
"last_ts": int(newest or after_ts or 0),
"person_id": int(person_identifier.person_id),
}
@@ -109,6 +133,7 @@ async def compose_ws_application(scope, receive, send):
service = str(payload.get("s") or "").strip()
identifier = str(payload.get("i") or "").strip()
person_id = str(payload.get("p") or "").strip()
resolved_person_id = _safe_int(person_id)
if user_id <= 0 or (not identifier and not person_id):
await send({"type": "websocket.close", "code": 4401})
@@ -117,6 +142,7 @@ async def compose_ws_application(scope, receive, send):
await send({"type": "websocket.accept"})
last_ts = 0
limit = 100
last_typing_key = ""
while True:
event = None
@@ -145,18 +171,33 @@ async def compose_ws_application(scope, receive, send):
)
messages = payload.get("messages") or []
latest = _safe_int(payload.get("last_ts"), last_ts)
if resolved_person_id <= 0:
resolved_person_id = _safe_int(payload.get("person_id"), 0)
typing_state = get_person_typing_state(
user_id=user_id,
person_id=resolved_person_id,
)
typing_key = json.dumps(typing_state, sort_keys=True)
typing_changed = typing_key != last_typing_key
if typing_changed:
last_typing_key = typing_key
outgoing_payload = {}
if messages:
last_ts = max(last_ts, latest)
outgoing_payload["messages"] = messages
outgoing_payload["last_ts"] = last_ts
else:
last_ts = max(last_ts, latest)
outgoing_payload["last_ts"] = last_ts
if typing_changed:
outgoing_payload["typing"] = typing_state
if messages or typing_changed:
await send(
{
"type": "websocket.send",
"text": json.dumps(
{
"messages": messages,
"last_ts": last_ts,
}
),
"text": json.dumps(outgoing_payload),
}
)
else:
last_ts = max(last_ts, latest)