maybe fix chat

This commit is contained in:
ajvpot
2025-08-01 07:03:59 +02:00
parent 8c05904bc4
commit b0480ec484
3 changed files with 68 additions and 62 deletions

View File

@@ -1,5 +1,5 @@
"use client";
import { useState, useEffect } from "react";
import { useState, useEffect, useCallback, useRef } from "react";
import { MinusIcon, PlusIcon } from "@heroicons/react/24/outline";
import { useConfig } from "./ConfigContext";
import { decryptMeshcoreGroupMessage } from "../lib/meshcore";
@@ -51,6 +51,7 @@ export default function ChatBox({ showAllMessagesTab = false, className = "", st
const [loading, setLoading] = useState(false);
const [hasMore, setHasMore] = useState(true);
const [lastBefore, setLastBefore] = useState<string | undefined>(undefined);
const messagesRef = useRef<ChatMessage[]>([]);
const selectedKey = allTabs[selectedTab];
const channelId = selectedKey.isAllMessages ? undefined : getChannelIdFromKey(selectedKey.privateKey).toUpperCase();
@@ -58,6 +59,59 @@ export default function ChatBox({ showAllMessagesTab = false, className = "", st
// Only show tabs if more than one channel (or if we have all messages tab)
const showTabs = allTabs.length > 1;
// Update ref when messages change
useEffect(() => {
messagesRef.current = messages;
}, [messages]);
const fetchMessages = useCallback(async (before?: string, replace = false, fetchNewer = false) => {
setLoading(true);
try {
let url = `/api/chat?limit=${PAGE_SIZE}`;
if (channelId) url += `&channel_id=${channelId}`;
if (fetchNewer) {
// Fetch newer messages using the most recent message timestamp
const mostRecentTimestamp = messagesRef.current.length > 0 ? messagesRef.current[0].ingest_timestamp : undefined;
if (mostRecentTimestamp) {
// Ensure we use the exact UTC timestamp format for the API
url += `&after=${encodeURIComponent(mostRecentTimestamp)}`;
}
} else if (before) {
// Fetch older messages using before parameter
url += `&before=${encodeURIComponent(before)}`;
}
const res = await fetch(buildApiUrl(url));
const data = await res.json();
if (Array.isArray(data)) {
if (fetchNewer && data.length > 0) {
// Add newer messages to the beginning (most recent first)
setMessages((prev) => [...data, ...prev]);
} else {
setMessages((prev) => replace ? data : [...prev, ...data]);
setHasMore(data.length === PAGE_SIZE);
if (data.length > 0) {
setLastBefore(data[data.length - 1].ingest_timestamp);
}
}
} else {
setHasMore(false);
}
} catch (error) {
// Only set hasMore to false if we don't have a lastBefore value (can't load more)
if (!lastBefore) {
setHasMore(false);
}
if (fetchNewer) {
// Silently fail for auto-refresh
console.error('Auto-refresh failed:', error);
}
} finally {
setLoading(false);
}
}, [channelId]);
useEffect(() => {
if (!minimized) {
setMessages([]);
@@ -79,57 +133,6 @@ export default function ChatBox({ showAllMessagesTab = false, className = "", st
}
}, [minimized, channelId]);
const fetchMessages = async (before?: string, replace = false, fetchNewer = false) => {
setLoading(true);
try {
let url = `/api/chat?limit=${PAGE_SIZE}`;
if (channelId) url += `&channel_id=${channelId}`;
if (fetchNewer) {
// Fetch newer messages using the most recent message timestamp
const mostRecentTimestamp = messages.length > 0 ? messages[0].ingest_timestamp : undefined;
if (mostRecentTimestamp) {
// Ensure we use the exact UTC timestamp format for the API
url += `&after=${encodeURIComponent(mostRecentTimestamp)}`;
}
} else if (before) {
// Fetch older messages using before parameter
url += `&before=${encodeURIComponent(before)}`;
}
const res = await fetch(buildApiUrl(url));
const data = await res.json();
if (Array.isArray(data)) {
if (fetchNewer && data.length > 0) {
// Filter out any duplicate messages by ingest_timestamp to prevent duplicates
const existingTimestamps = new Set(messages.map(msg => msg.ingest_timestamp));
const newMessages = data.filter(msg => !existingTimestamps.has(msg.ingest_timestamp));
if (newMessages.length > 0) {
// Add newer messages to the beginning (most recent first)
setMessages((prev) => [...newMessages, ...prev]);
}
} else {
setMessages((prev) => replace ? data : [...prev, ...data]);
setHasMore(data.length === PAGE_SIZE);
if (data.length > 0) {
setLastBefore(data[data.length - 1].ingest_timestamp);
}
}
} else {
setHasMore(false);
}
} catch (error) {
setHasMore(false);
if (fetchNewer) {
// Silently fail for auto-refresh
console.error('Auto-refresh failed:', error);
}
} finally {
setLoading(false);
}
};
const handleLoadMore = () => {
if (lastBefore) {
fetchMessages(lastBefore);

View File

@@ -1,5 +1,5 @@
"use client";
import { useState, useEffect } from "react";
import { useState, useEffect, useMemo } from "react";
import { useConfig } from "./ConfigContext";
import { decryptMeshcoreGroupMessage } from "../lib/meshcore";
@@ -26,10 +26,11 @@ function formatLocalTime(utcString: string): string {
export default function ChatMessageItem({ msg, showErrorRow }: { msg: ChatMessage, showErrorRow?: boolean }) {
const { config } = useConfig();
const knownKeys = [
const knownKeys = useMemo(() => [
...(config?.meshcoreKeys?.map((k: any) => k.privateKey) || []),
"izOH6cXN6mrJ5e26oRXNcg==", // Always include public key
];
], [config?.meshcoreKeys]);
const knownKeysString = knownKeys.join(",");
const [parsed, setParsed] = useState<any | null>(null);
const [error, setError] = useState<string | null>(null);
const [originsExpanded, setOriginsExpanded] = useState(false);
@@ -62,7 +63,7 @@ export default function ChatMessageItem({ msg, showErrorRow }: { msg: ChatMessag
}
})();
return () => { cancelled = true; };
}, [msg.encrypted_message, msg.mac, msg.channel_hash, knownKeys.join(",")]);
}, [msg.encrypted_message, msg.mac, msg.channel_hash, knownKeysString, knownKeys]);
const originPathArray = msg.origin_path_array && msg.origin_path_array.length > 0 ? msg.origin_path_array : [];
const originsCount = originPathArray.length;

View File

@@ -1,7 +1,7 @@
"use client";
import { MapContainer, TileLayer, useMapEvents, Marker, Popup, MapContainerProps, useMap } from "react-leaflet";
import { useRouter, useSearchParams } from "next/navigation";
import { useEffect, useMemo, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState, useCallback } from "react";
import 'leaflet/dist/leaflet.css';
import L from "leaflet";
import 'leaflet.markercluster/dist/leaflet.markercluster.js';
@@ -33,7 +33,8 @@ type NodePosition = {
type ClusteredMarkersProps = { nodes: NodePosition[] };
function ClusteredMarkers({ nodes }: ClusteredMarkersProps) {
const map = useMap();
const { config } = useConfig ? useConfig() : { config: undefined };
const configResult = useConfig();
const config = configResult?.config;
useEffect(() => {
if (!map) return;
// Remove any previous layers
@@ -107,7 +108,8 @@ export default function MapView() {
const [lastResultCount, setLastResultCount] = useState<number>(0);
const fetchController = useRef<AbortController | null>(null);
const lastRequestedBounds = useRef<[[number, number], [number, number]] | null>(null);
const { config } = useConfig ? useConfig() : { config: undefined };
const configResult = useConfig();
const config = configResult?.config;
type TileLayerKey = 'openstreetmap' | 'opentopomap' | 'esri';
const tileLayerOptions: Record<TileLayerKey, { url: string; attribution: string; maxZoom: number; subdomains?: string[] }> = {
@@ -129,7 +131,7 @@ export default function MapView() {
};
const selectedTileLayer = tileLayerOptions[(config?.tileLayer as TileLayerKey) || 'openstreetmap'];
function fetchNodes(bounds?: [[number, number], [number, number]]) {
const fetchNodes = useCallback((bounds?: [[number, number], [number, number]]) => {
if (fetchController.current) {
fetchController.current.abort();
}
@@ -169,7 +171,7 @@ export default function MapView() {
if (err.name !== "AbortError") setNodePositions([]);
if (fetchController.current === controller) setLoading(false);
});
}
}, [config?.nodeTypes, config?.lastSeen]);
function isBoundsInside(inner: [[number, number], [number, number]], outer: [[number, number], [number, number]]) {
// inner: [[minLat, minLng], [maxLat, maxLng]]
@@ -264,7 +266,7 @@ export default function MapView() {
return () => {
fetchController.current?.abort();
};
}, [bounds, config?.nodeTypes, config?.lastSeen]);
}, [bounds, config?.nodeTypes, config?.lastSeen, fetchNodes]);
return (
<div style={{ width: "100%", height: "100%", position: "relative" }}>