diff --git a/meshview/static/api-chat.html b/meshview/static/api-chat.html
new file mode 100644
index 0000000..fe9dd81
--- /dev/null
+++ b/meshview/static/api-chat.html
@@ -0,0 +1,127 @@
+
+
+
+
+ API Documentation - Chat
+
+
+
+
+
+
+
+
+
+
diff --git a/meshview/static/api-edges.html b/meshview/static/api-edges.html
new file mode 100644
index 0000000..e1fcec2
--- /dev/null
+++ b/meshview/static/api-edges.html
@@ -0,0 +1,109 @@
+
+
+
+
+ API Documentation - Edges
+
+
+
+
+
+
+
+
+
+
diff --git a/meshview/static/api-nodes.html b/meshview/static/api-nodes.html
new file mode 100644
index 0000000..9dc7d11
--- /dev/null
+++ b/meshview/static/api-nodes.html
@@ -0,0 +1,134 @@
+
+
+
+
+ API Documentation - Nodes
+
+
+
+
+
+
+
+
+
+
diff --git a/meshview/templates/chat.html b/meshview/templates/chat.html
index f72fbf4..d44d8b1 100644
--- a/meshview/templates/chat.html
+++ b/meshview/templates/chat.html
@@ -23,135 +23,152 @@
animation: flash 3.5s ease-out;
}
-/* New CSS for smaller Replying to text */
+/* Nested reply style below the message */
.replying-to {
- font-size: 0.85em;
- color: #ccc;
+ background-color: #444;
+ padding: 4px 8px;
+ margin-top: 4px;
+ border-left: 3px solid #ffe066;
+ border-radius: 4px;
+ font-size: 0.9em;
+ color: #fff;
}
{% endblock %}
{% block body %}
- {% for packet in packets %}
-
- {% else %}
-
No packets found.
- {% endfor %}
+
+
+
+
{% endblock %}
diff --git a/meshview/web.py b/meshview/web.py
index 0504606..3abce95 100644
--- a/meshview/web.py
+++ b/meshview/web.py
@@ -1279,18 +1279,9 @@ async def top(request):
@routes.get("/chat")
async def chat(request):
try:
- packets = await store.get_packets(
- node_id=0xFFFFFFFF, portnum=PortNum.TEXT_MESSAGE_APP, limit=100
- )
-
- ui_packets = [Packet.from_model(p) for p in packets]
- filtered_packets = [
- p for p in ui_packets if not re.fullmatch(r"seq \d+", p.payload)
- ]
template = env.get_template("chat.html")
return web.Response(
text=template.render(
- packets=filtered_packets,
site_config=CONFIG,
SOFTWARE_RELEASE=SOFTWARE_RELEASE
),
@@ -1307,63 +1298,6 @@ async def chat(request):
)
return web.Response(text=rendered, status=500, content_type="text/html")
-@routes.get("/chat/updates")
-async def chat_updates(request):
- try:
- last_time_str = request.query.get("last_time", None)
- if last_time_str:
- try:
- last_time = datetime.datetime.fromisoformat(last_time_str)
- except Exception as e:
- print(f"Failed to parse last_time '{last_time_str}': {e}")
- last_time = datetime.datetime.min
- else:
- last_time = datetime.datetime.min
-
- packets = await store.get_packets(
- node_id=0xFFFFFFFF,
- portnum=PortNum.TEXT_MESSAGE_APP,
- limit=5,
- )
- ui_packets = [Packet.from_model(p) for p in packets]
- filtered_packets = [p for p in ui_packets if not SEQ_REGEX.fullmatch(p.payload)]
- new_packets = [p for p in filtered_packets if p.import_time > last_time]
-
- packets_data = []
- for p in new_packets:
- reply_id = None
- if getattr(p, 'raw_mesh_packet', None):
- decoded = getattr(p.raw_mesh_packet, 'decoded', None)
- if decoded:
- reply_id = getattr(decoded, 'reply_id', None)
-
- packet_dict = {
- "id": p.id,
- "import_time": p.import_time.isoformat(),
- "channel": p.from_node.channel if p.from_node else "",
- "from_node_id": p.from_node_id,
- "long_name": p.from_node.long_name if p.from_node else "",
- "payload": p.payload,
- }
- if reply_id:
- packet_dict["reply_id"] = reply_id
-
- packets_data.append(packet_dict)
-
- response = {
- "packets": packets_data
- }
-
- if new_packets:
- latest_import_time = max(p.import_time for p in new_packets)
- response["latest_import_time"] = latest_import_time.isoformat()
-
- return web.json_response(response)
-
- except Exception as e:
- print("Error in /chat/updates:", e)
- return web.json_response({"error": "Failed to fetch updates"}, status=500)
-
# Assuming the route URL structure is /nodegraph
@routes.get("/nodegraph")
@@ -1476,41 +1410,48 @@ async def get_config(request):
# The response includes "latest_import_time" for frontend to keep track of the newest message timestamp.
# The backend fetches extra packets (limit*5) to account for filtering messages like "seq N" and since filtering.
+
@routes.get("/api/chat")
async def api_chat(request):
try:
# Parse query params
- limit_str = request.query.get("limit", "100")
- since_str = request.query.get("since", None)
+ limit_str = request.query.get("limit", "20")
+ since_str = request.query.get("since")
+ # Clamp limit between 1 and 200
try:
- limit = min(max(int(limit_str), 1), 200) # Limit between 1 and 200
+ limit = min(max(int(limit_str), 1), 100)
except ValueError:
- limit = 100
+ limit = 50
+ # Parse "since" timestamp if provided
+ since = None
if since_str:
try:
since = datetime.datetime.fromisoformat(since_str)
except Exception as e:
print(f"Failed to parse since '{since_str}': {e}")
- since = None
- else:
- since = None
# Fetch packets from store
packets = await store.get_packets(
node_id=0xFFFFFFFF,
portnum=PortNum.TEXT_MESSAGE_APP,
- limit=limit*5, # Fetch extra to filter out seq messages and since filter
+ limit=limit,
)
- SEQ_REGEX = re.compile(r"seq \d+")
ui_packets = [Packet.from_model(p) for p in packets]
- filtered_packets = [p for p in ui_packets if p.payload and not SEQ_REGEX.fullmatch(p.payload)]
- # Filter by 'since' timestamp if provided
+ # Filter out "seq N" and missing payloads
+ filtered_packets = [
+ p for p in ui_packets
+ if p.payload and not SEQ_REGEX.fullmatch(p.payload)
+ ]
+
+ # Apply "since" filter
if since:
- filtered_packets = [p for p in filtered_packets if p.import_time > since]
+ filtered_packets = [
+ p for p in filtered_packets if p.import_time > since
+ ]
# Sort by import_time descending (latest first)
filtered_packets.sort(key=lambda p: p.import_time, reverse=True)
@@ -1518,16 +1459,36 @@ async def api_chat(request):
# Trim to requested limit
filtered_packets = filtered_packets[:limit]
- packets_data = [{
- "id": p.id,
- "import_time": p.import_time.isoformat(),
- "channel": getattr(p.from_node, "channel", ""),
- "from_node_id": p.from_node_id,
- "long_name": getattr(p.from_node, "long_name", ""),
- "payload": p.payload,
- } for p in filtered_packets]
+ # Build response data
+ packets_data = []
+ for p in filtered_packets:
+ reply_id = getattr(
+ getattr(getattr(p, "raw_mesh_packet", None), "decoded", None),
+ "reply_id",
+ None
+ )
- latest_import_time = filtered_packets[0].import_time.isoformat() if filtered_packets else since_str or None
+ packet_dict = {
+ "id": p.id,
+ "import_time": p.import_time.isoformat(),
+ "channel": getattr(p.from_node, "channel", ""),
+ "from_node_id": p.from_node_id,
+ "long_name": getattr(p.from_node, "long_name", ""),
+ "payload": p.payload,
+ }
+
+ if reply_id: # ✅ only include if not None/0
+ packet_dict["reply_id"] = reply_id
+
+ packets_data.append(packet_dict)
+
+ # Pick latest import time for clients to use in next request
+ if filtered_packets:
+ latest_import_time = filtered_packets[0].import_time.isoformat()
+ elif since:
+ latest_import_time = since.isoformat()
+ else:
+ latest_import_time = None
return web.json_response({
"packets": packets_data,
@@ -1536,7 +1497,11 @@ async def api_chat(request):
except Exception as e:
print("Error in /api/chat:", e)
- return web.json_response({"error": "Failed to fetch chat data"}, status=500)
+ return web.json_response(
+ {"error": "Failed to fetch chat data", "details": str(e)},
+ status=500
+ )
+
# Client to pass ?hours=1 or ?days=7 to filter