更新bug
This commit is contained in:
@@ -14,6 +14,7 @@ import json
|
||||
import os
|
||||
import time
|
||||
import threading
|
||||
import copy
|
||||
|
||||
# 每个会话最多留存的消息条数(防止档案无限膨胀;AI 实际使用条数由 AI_CONTEXT_MAX_ROUNDS 决定)
|
||||
MAX_MESSAGES_PER_SESSION = 200
|
||||
@@ -95,6 +96,47 @@ class ConversationStore:
|
||||
e["history"] = e["history"][-MAX_MESSAGES_PER_SESSION:]
|
||||
e["updated"] = time.time()
|
||||
|
||||
def append_exchange_once(
|
||||
self,
|
||||
fp_hex: str,
|
||||
user_text: str,
|
||||
assistant_text: str,
|
||||
exchange_id: str,
|
||||
) -> bool:
|
||||
"""Atomically append one user/assistant pair once across crash recovery."""
|
||||
key = str(fp_hex or "")
|
||||
txid = str(exchange_id or "")
|
||||
if not key or not txid:
|
||||
return False
|
||||
self._maybe_reload()
|
||||
appended = False
|
||||
with self._lock:
|
||||
entry = self._data.setdefault(key, {
|
||||
"history": [],
|
||||
"last_lines": [],
|
||||
"updated": 0,
|
||||
})
|
||||
committed = entry.setdefault("exchange_ids", [])
|
||||
if txid not in committed:
|
||||
now = time.time()
|
||||
entry["history"].extend([
|
||||
{"role": "user", "content": str(user_text or ""), "ts": now},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": str(assistant_text or ""),
|
||||
"ts": now,
|
||||
},
|
||||
])
|
||||
if len(entry["history"]) > MAX_MESSAGES_PER_SESSION:
|
||||
entry["history"] = entry["history"][-MAX_MESSAGES_PER_SESSION:]
|
||||
committed.append(txid)
|
||||
entry["exchange_ids"] = committed[-MAX_MESSAGES_PER_SESSION:]
|
||||
entry["updated"] = now
|
||||
appended = True
|
||||
if appended:
|
||||
self.save()
|
||||
return appended
|
||||
|
||||
def last_lines(self, fp_hex: str) -> list:
|
||||
return self._entry(fp_hex)["last_lines"]
|
||||
|
||||
@@ -103,6 +145,34 @@ class ConversationStore:
|
||||
e["last_lines"] = list(lines)[-MAX_SNAPSHOT_LINES:]
|
||||
e["updated"] = time.time()
|
||||
|
||||
def outgoing_speakers(self, fp_hex: str) -> list[str]:
|
||||
"""Return sender labels previously proven by right-side bubble geometry."""
|
||||
entry = self._entry(fp_hex)
|
||||
return [
|
||||
str(value).strip()
|
||||
for value in entry.get("outgoing_speakers") or []
|
||||
if str(value).strip()
|
||||
]
|
||||
|
||||
def add_outgoing_speaker(self, fp_hex: str, speaker: str) -> bool:
|
||||
"""Persist one visually proven local sender label for this conversation."""
|
||||
name = str(speaker or "").strip()
|
||||
if not name:
|
||||
return False
|
||||
entry = self._entry(fp_hex)
|
||||
known = {
|
||||
str(value).strip()
|
||||
for value in entry.get("outgoing_speakers") or []
|
||||
if str(value).strip()
|
||||
}
|
||||
if name in known:
|
||||
return False
|
||||
known.add(name)
|
||||
entry["outgoing_speakers"] = sorted(known)
|
||||
entry["updated"] = time.time()
|
||||
self.save()
|
||||
return True
|
||||
|
||||
def list_sessions(self, limit: int = 200) -> list:
|
||||
"""按最近更新排序,返回会话摘要列表。"""
|
||||
self._maybe_reload()
|
||||
@@ -139,6 +209,46 @@ class ConversationStore:
|
||||
self.save()
|
||||
return True
|
||||
|
||||
def migrate_key(self, old_fp_hex: str, new_fp_hex: str) -> bool:
|
||||
"""Atomically move one legacy session entry to a stronger identity key."""
|
||||
old_key = str(old_fp_hex or "")
|
||||
new_key = str(new_fp_hex or "")
|
||||
if not old_key or not new_key or old_key == new_key:
|
||||
return False
|
||||
self._maybe_reload()
|
||||
migrated = False
|
||||
with self._lock:
|
||||
current = self._data.get(new_key)
|
||||
current_is_empty_shell = bool(
|
||||
isinstance(current, dict)
|
||||
and not current.get("history")
|
||||
and not current.get("last_lines")
|
||||
and not current.get("exchange_ids")
|
||||
)
|
||||
if old_key in self._data and (
|
||||
new_key not in self._data or current_is_empty_shell
|
||||
):
|
||||
if current_is_empty_shell:
|
||||
self._data.pop(new_key, None)
|
||||
self._data[new_key] = self._data.pop(old_key)
|
||||
migrated = True
|
||||
if migrated:
|
||||
self.save()
|
||||
return migrated
|
||||
|
||||
def entry_snapshot(self, fp_hex: str) -> dict | None:
|
||||
"""Return a detached legacy entry for identity checks without creating it."""
|
||||
self._maybe_reload()
|
||||
entry = self._data.get(str(fp_hex or ""))
|
||||
if not isinstance(entry, dict):
|
||||
return None
|
||||
return copy.deepcopy(entry)
|
||||
|
||||
def keys(self) -> list[str]:
|
||||
"""Return a detached list of stored session keys without exposing live data."""
|
||||
self._maybe_reload()
|
||||
return [str(key) for key in (self._data or {}).keys()]
|
||||
|
||||
def clear_all(self) -> int:
|
||||
"""清空全部会话档案,返回删除条数。"""
|
||||
self._maybe_reload()
|
||||
|
||||
Reference in New Issue
Block a user