fix(chat): chat_backup ts auf UNIX-ms umgestellt + Doppelpost-Schutz
Bug-1: _append_chat_backup nutzte asyncio.get_event_loop().time() — das ist Container-Monotonic (bei Restart wieder 0), NICHT UNIX-Zeit. Bridge schrieb so Eintraege mit ts wie 394M (=6.5 min Uptime), App-side generiert User-Bubbles mit Date.now() = 1.778e12. Beim Sortieren in der App: Server-Bubbles landeten alle als "uralt" (kleine ts) ueber den lokalen Bubbles und teilweise unter dem 500er-Cap raus — Symptom: "alles nach Hello Kitty fehlt in der App". Fix: _append_chat_backup nutzt jetzt time.time() * 1000 (UNIX-ms). Bug-2: doppelte User-Bubble nach App-Hintergrund/Restart mit Retry-Knopf. Race-Fix von vorhin (text+timestamp-Heuristik, 5-Min-Fenster) griff nicht weil bei kaputten Server-ts (394M) und lokalen UNIX-ms (1.778e12) das Diff 1.7 Billionen ms war → Fenster nie zutreffend → lokale Bubble blieb als Duplikat. Fix: Text-Match alleine reicht — wenn der Server irgendwo eine textgleiche User-Bubble hat, ist es dieselbe Nachricht. Greift jetzt unabhaengig von ts-Konsistenz. Plus: tools/migrate_chat_backup_ts.py — repariert vorhandene jsonl (284 von 299 Eintraege auf der VM hatten Container-Uptime-ts). Datei- Reihenfolge bleibt erhalten (war eh chronologisch), ts werden ab File- Mtime rueckwaerts 60s-Schritten vergeben. Idempotent, .bak-Backup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -686,23 +686,26 @@ const ChatScreen: React.FC = () => {
|
||||
// gesetzt UND text leer/Placeholder)
|
||||
// - User-Bubbles deren clientMsgId der Server noch nicht kennt:
|
||||
// z.B. waehrend Reconnect-Race oder solange flushQueuedMessages
|
||||
// noch laeuft. ABER: wenn der Server eine textgleiche Bubble
|
||||
// im gleichen 5-Min-Fenster hat (Alter Backup-Eintrag ohne
|
||||
// clientMsgId, vor dem Bridge-Patch geschrieben), werten wir
|
||||
// das als Treffer und verwerfen die lokale Kopie — sonst
|
||||
// Doppelpost: einmal als Server-Bubble (delivered) und einmal
|
||||
// als lokale failed/queued mit Retry-Knopf.
|
||||
const FIVE_MIN = 5 * 60 * 1000;
|
||||
// noch laeuft. ABER: wenn der Server eine textgleiche User-
|
||||
// Bubble hat (egal mit welcher cmid oder ohne — z.B. wenn die
|
||||
// Bubble aus einer Bridge-Version vor dem clientMsgId-Patch
|
||||
// stammt oder wenn die ts kaputt sind), werten wir das als
|
||||
// Treffer und verwerfen die lokale Kopie. Sonst Doppelpost:
|
||||
// einmal als Server-Bubble (delivered) und einmal als lokale
|
||||
// failed/queued mit Retry-Knopf.
|
||||
const serverUserTexts = new Set(
|
||||
fromServer.filter(s => s.sender === 'user').map(s => s.text || '')
|
||||
);
|
||||
const localOnly = prev.filter(m => {
|
||||
if (m.skillCreated || m.triggerCreated || m.memorySaved) return true;
|
||||
if (m.audioRequestId && (!m.text || m.text === '🎙 Aufnahme...' || m.text === 'Aufnahme...')) return true;
|
||||
if (m.sender === 'user' && m.clientMsgId && !serverCmids.has(m.clientMsgId)) {
|
||||
const serverHasIt = fromServer.some(s =>
|
||||
s.sender === 'user' &&
|
||||
s.text === m.text &&
|
||||
Math.abs((s.timestamp || 0) - (m.timestamp || 0)) < FIVE_MIN,
|
||||
);
|
||||
if (serverHasIt) return false;
|
||||
// Text-Match-Fallback: wenn der Server irgendwo eine textgleiche
|
||||
// User-Bubble hat, ist es dieselbe Nachricht (vor cmid-Aera, ts
|
||||
// kaputt etc.) — wir verwerfen die lokale Kopie. Leerer Text
|
||||
// (z.B. nur Anhang) faellt nicht in den Vergleich.
|
||||
const text = m.text || '';
|
||||
if (text && serverUserTexts.has(text)) return false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user