index_entity_mixin.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. IndexEntityMixin extracted from IndexManager.
  5. """
  6. from __future__ import annotations
  7. import json
  8. import sys
  9. from datetime import datetime
  10. from typing import Any, Dict, List, Optional
  11. class IndexEntityMixin:
  12. def upsert_entity(self, entity: EntityMeta, update_metadata: bool = False) -> bool:
  13. """
  14. 插入或更新实体 (智能合并)
  15. - 新实体: 直接插入
  16. - 已存在: 更新 current_json, last_appearance, updated_at
  17. - update_metadata=True: 同时更新 canonical_name/tier/desc/is_protagonist/is_archived
  18. 返回是否为新实体
  19. """
  20. with self._get_conn() as conn:
  21. cursor = conn.cursor()
  22. # 检查是否存在
  23. cursor.execute(
  24. "SELECT id, current_json FROM entities WHERE id = ?", (entity.id,)
  25. )
  26. existing = cursor.fetchone()
  27. if existing:
  28. # 已存在: 智能合并 current_json
  29. old_current = {}
  30. if existing["current_json"]:
  31. try:
  32. old_current = json.loads(existing["current_json"])
  33. except json.JSONDecodeError as exc:
  34. print(f"[index_manager] failed to parse JSON in entities.current_json: {exc}", file=sys.stderr)
  35. # 合并 current (新值覆盖旧值)
  36. merged_current = {**old_current, **entity.current}
  37. if update_metadata:
  38. # 完整更新(包括元数据)
  39. cursor.execute(
  40. """
  41. UPDATE entities SET
  42. canonical_name = ?,
  43. tier = ?,
  44. desc = ?,
  45. current_json = ?,
  46. last_appearance = ?,
  47. is_protagonist = ?,
  48. is_archived = ?,
  49. updated_at = CURRENT_TIMESTAMP
  50. WHERE id = ?
  51. """,
  52. (
  53. entity.canonical_name,
  54. entity.tier,
  55. entity.desc,
  56. json.dumps(merged_current, ensure_ascii=False),
  57. entity.last_appearance,
  58. 1 if entity.is_protagonist else 0,
  59. 1 if entity.is_archived else 0,
  60. entity.id,
  61. ),
  62. )
  63. else:
  64. # 只更新 current 和 last_appearance
  65. cursor.execute(
  66. """
  67. UPDATE entities SET
  68. current_json = ?,
  69. last_appearance = ?,
  70. updated_at = CURRENT_TIMESTAMP
  71. WHERE id = ?
  72. """,
  73. (
  74. json.dumps(merged_current, ensure_ascii=False),
  75. entity.last_appearance,
  76. entity.id,
  77. ),
  78. )
  79. conn.commit()
  80. return False
  81. else:
  82. # 新实体: 插入
  83. cursor.execute(
  84. """
  85. INSERT INTO entities
  86. (id, type, canonical_name, tier, desc, current_json,
  87. first_appearance, last_appearance, is_protagonist, is_archived)
  88. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
  89. """,
  90. (
  91. entity.id,
  92. entity.type,
  93. entity.canonical_name,
  94. entity.tier,
  95. entity.desc,
  96. json.dumps(entity.current, ensure_ascii=False),
  97. entity.first_appearance,
  98. entity.last_appearance,
  99. 1 if entity.is_protagonist else 0,
  100. 1 if entity.is_archived else 0,
  101. ),
  102. )
  103. conn.commit()
  104. return True
  105. def get_entity(self, entity_id: str) -> Optional[Dict]:
  106. """获取单个实体"""
  107. with self._get_conn() as conn:
  108. cursor = conn.cursor()
  109. cursor.execute("SELECT * FROM entities WHERE id = ?", (entity_id,))
  110. row = cursor.fetchone()
  111. if row:
  112. return self._row_to_dict(row, parse_json=["current_json"])
  113. return None
  114. def get_entities_by_type(
  115. self, entity_type: str, include_archived: bool = False
  116. ) -> List[Dict]:
  117. """按类型获取实体"""
  118. with self._get_conn() as conn:
  119. cursor = conn.cursor()
  120. if include_archived:
  121. cursor.execute(
  122. """
  123. SELECT * FROM entities WHERE type = ?
  124. ORDER BY last_appearance DESC
  125. """,
  126. (entity_type,),
  127. )
  128. else:
  129. cursor.execute(
  130. """
  131. SELECT * FROM entities WHERE type = ? AND is_archived = 0
  132. ORDER BY last_appearance DESC
  133. """,
  134. (entity_type,),
  135. )
  136. return [
  137. self._row_to_dict(row, parse_json=["current_json"])
  138. for row in cursor.fetchall()
  139. ]
  140. def get_entities_by_tier(self, tier: str) -> List[Dict]:
  141. """按重要度获取实体 (核心/重要/次要/装饰)"""
  142. with self._get_conn() as conn:
  143. cursor = conn.cursor()
  144. cursor.execute(
  145. """
  146. SELECT * FROM entities WHERE tier = ? AND is_archived = 0
  147. ORDER BY last_appearance DESC
  148. """,
  149. (tier,),
  150. )
  151. return [
  152. self._row_to_dict(row, parse_json=["current_json"])
  153. for row in cursor.fetchall()
  154. ]
  155. def get_core_entities(self) -> List[Dict]:
  156. """获取所有核心实体 (用于 Context Agent 全量加载)"""
  157. with self._get_conn() as conn:
  158. cursor = conn.cursor()
  159. cursor.execute("""
  160. SELECT * FROM entities
  161. WHERE (tier IN ('核心', '重要') OR is_protagonist = 1) AND is_archived = 0
  162. ORDER BY is_protagonist DESC, tier, last_appearance DESC
  163. """)
  164. return [
  165. self._row_to_dict(row, parse_json=["current_json"])
  166. for row in cursor.fetchall()
  167. ]
  168. def get_protagonist(self) -> Optional[Dict]:
  169. """获取主角实体"""
  170. with self._get_conn() as conn:
  171. cursor = conn.cursor()
  172. cursor.execute("SELECT * FROM entities WHERE is_protagonist = 1 LIMIT 1")
  173. row = cursor.fetchone()
  174. if row:
  175. return self._row_to_dict(row, parse_json=["current_json"])
  176. return None
  177. def update_entity_current(self, entity_id: str, updates: Dict) -> bool:
  178. """
  179. 增量更新实体的 current 字段 (不覆盖其他字段)
  180. 例如: update_entity_current("xiaoyan", {"realm": "斗师"})
  181. """
  182. with self._get_conn() as conn:
  183. cursor = conn.cursor()
  184. cursor.execute(
  185. "SELECT current_json FROM entities WHERE id = ?", (entity_id,)
  186. )
  187. row = cursor.fetchone()
  188. if not row:
  189. return False
  190. current = {}
  191. if row["current_json"]:
  192. try:
  193. current = json.loads(row["current_json"])
  194. except json.JSONDecodeError as exc:
  195. print(
  196. f"[index_manager] failed to parse JSON in update_entity_current current_json: {exc}",
  197. file=sys.stderr,
  198. )
  199. current.update(updates)
  200. cursor.execute(
  201. """
  202. UPDATE entities SET
  203. current_json = ?,
  204. updated_at = CURRENT_TIMESTAMP
  205. WHERE id = ?
  206. """,
  207. (json.dumps(current, ensure_ascii=False), entity_id),
  208. )
  209. conn.commit()
  210. return True
  211. def archive_entity(self, entity_id: str) -> bool:
  212. """归档实体 (不删除,只是标记)"""
  213. with self._get_conn() as conn:
  214. cursor = conn.cursor()
  215. cursor.execute(
  216. """
  217. UPDATE entities SET is_archived = 1, updated_at = CURRENT_TIMESTAMP
  218. WHERE id = ?
  219. """,
  220. (entity_id,),
  221. )
  222. conn.commit()
  223. return cursor.rowcount > 0
  224. # ==================== v5.1 别名操作 ====================
  225. def register_alias(self, alias: str, entity_id: str, entity_type: str) -> bool:
  226. """
  227. 注册别名 (支持一对多)
  228. 同一别名可映射多个实体 (如 "天云宗" → 地点 + 势力)
  229. """
  230. with self._get_conn() as conn:
  231. cursor = conn.cursor()
  232. try:
  233. cursor.execute(
  234. """
  235. INSERT OR IGNORE INTO aliases (alias, entity_id, entity_type)
  236. VALUES (?, ?, ?)
  237. """,
  238. (alias, entity_id, entity_type),
  239. )
  240. conn.commit()
  241. return cursor.rowcount > 0
  242. except sqlite3.IntegrityError:
  243. return False
  244. def get_entities_by_alias(self, alias: str) -> List[Dict]:
  245. """
  246. 根据别名查找实体 (一对多)
  247. 返回所有匹配的实体 (可能有多个不同类型)
  248. """
  249. with self._get_conn() as conn:
  250. cursor = conn.cursor()
  251. cursor.execute(
  252. """
  253. SELECT e.*, a.entity_type as alias_type
  254. FROM entities e
  255. JOIN aliases a ON e.id = a.entity_id
  256. WHERE a.alias = ?
  257. """,
  258. (alias,),
  259. )
  260. return [
  261. self._row_to_dict(row, parse_json=["current_json"])
  262. for row in cursor.fetchall()
  263. ]
  264. def get_entity_aliases(self, entity_id: str) -> List[str]:
  265. """获取实体的所有别名"""
  266. with self._get_conn() as conn:
  267. cursor = conn.cursor()
  268. cursor.execute(
  269. "SELECT alias FROM aliases WHERE entity_id = ?", (entity_id,)
  270. )
  271. return [row["alias"] for row in cursor.fetchall()]
  272. def remove_alias(self, alias: str, entity_id: str) -> bool:
  273. """移除别名"""
  274. with self._get_conn() as conn:
  275. cursor = conn.cursor()
  276. cursor.execute(
  277. "DELETE FROM aliases WHERE alias = ? AND entity_id = ?",
  278. (alias, entity_id),
  279. )
  280. conn.commit()
  281. return cursor.rowcount > 0
  282. # ==================== v5.1 状态变化操作 ====================
  283. def record_state_change(self, change: StateChangeMeta) -> int:
  284. """
  285. 记录状态变化
  286. 返回记录 ID
  287. """
  288. with self._get_conn() as conn:
  289. cursor = conn.cursor()
  290. cursor.execute(
  291. """
  292. INSERT INTO state_changes
  293. (entity_id, field, old_value, new_value, reason, chapter)
  294. VALUES (?, ?, ?, ?, ?, ?)
  295. """,
  296. (
  297. change.entity_id,
  298. change.field,
  299. change.old_value,
  300. change.new_value,
  301. change.reason,
  302. change.chapter,
  303. ),
  304. )
  305. conn.commit()
  306. return cursor.lastrowid
  307. def get_entity_state_changes(self, entity_id: str, limit: int = 20) -> List[Dict]:
  308. """获取实体的状态变化历史"""
  309. with self._get_conn() as conn:
  310. cursor = conn.cursor()
  311. cursor.execute(
  312. """
  313. SELECT * FROM state_changes
  314. WHERE entity_id = ?
  315. ORDER BY chapter DESC, id DESC
  316. LIMIT ?
  317. """,
  318. (entity_id, limit),
  319. )
  320. return [dict(row) for row in cursor.fetchall()]
  321. def get_recent_state_changes(self, limit: int = 50) -> List[Dict]:
  322. """获取最近的状态变化"""
  323. with self._get_conn() as conn:
  324. cursor = conn.cursor()
  325. cursor.execute(
  326. """
  327. SELECT * FROM state_changes
  328. ORDER BY chapter DESC, id DESC
  329. LIMIT ?
  330. """,
  331. (limit,),
  332. )
  333. return [dict(row) for row in cursor.fetchall()]
  334. def get_chapter_state_changes(self, chapter: int) -> List[Dict]:
  335. """获取某章的所有状态变化"""
  336. with self._get_conn() as conn:
  337. cursor = conn.cursor()
  338. cursor.execute(
  339. """
  340. SELECT * FROM state_changes
  341. WHERE chapter = ?
  342. ORDER BY id
  343. """,
  344. (chapter,),
  345. )
  346. return [dict(row) for row in cursor.fetchall()]
  347. # ==================== v5.1 关系操作 ====================
  348. def upsert_relationship(self, rel: RelationshipMeta) -> bool:
  349. """
  350. 插入或更新关系
  351. 相同 (from, to, type) 会更新 description 和 chapter
  352. 返回是否为新关系
  353. """
  354. with self._get_conn() as conn:
  355. cursor = conn.cursor()
  356. # 检查是否存在
  357. cursor.execute(
  358. """
  359. SELECT id FROM relationships
  360. WHERE from_entity = ? AND to_entity = ? AND type = ?
  361. """,
  362. (rel.from_entity, rel.to_entity, rel.type),
  363. )
  364. existing = cursor.fetchone()
  365. if existing:
  366. cursor.execute(
  367. """
  368. UPDATE relationships SET
  369. description = ?,
  370. chapter = ?
  371. WHERE id = ?
  372. """,
  373. (rel.description, rel.chapter, existing["id"]),
  374. )
  375. conn.commit()
  376. return False
  377. else:
  378. cursor.execute(
  379. """
  380. INSERT INTO relationships
  381. (from_entity, to_entity, type, description, chapter)
  382. VALUES (?, ?, ?, ?, ?)
  383. """,
  384. (
  385. rel.from_entity,
  386. rel.to_entity,
  387. rel.type,
  388. rel.description,
  389. rel.chapter,
  390. ),
  391. )
  392. conn.commit()
  393. return True
  394. def get_entity_relationships(
  395. self, entity_id: str, direction: str = "both"
  396. ) -> List[Dict]:
  397. """
  398. 获取实体的关系
  399. direction: "from" | "to" | "both"
  400. """
  401. with self._get_conn() as conn:
  402. cursor = conn.cursor()
  403. if direction == "from":
  404. cursor.execute(
  405. """
  406. SELECT * FROM relationships WHERE from_entity = ?
  407. ORDER BY chapter DESC
  408. """,
  409. (entity_id,),
  410. )
  411. elif direction == "to":
  412. cursor.execute(
  413. """
  414. SELECT * FROM relationships WHERE to_entity = ?
  415. ORDER BY chapter DESC
  416. """,
  417. (entity_id,),
  418. )
  419. else: # both
  420. cursor.execute(
  421. """
  422. SELECT * FROM relationships
  423. WHERE from_entity = ? OR to_entity = ?
  424. ORDER BY chapter DESC
  425. """,
  426. (entity_id, entity_id),
  427. )
  428. return [dict(row) for row in cursor.fetchall()]
  429. def get_relationship_between(self, entity1: str, entity2: str) -> List[Dict]:
  430. """获取两个实体之间的所有关系"""
  431. with self._get_conn() as conn:
  432. cursor = conn.cursor()
  433. cursor.execute(
  434. """
  435. SELECT * FROM relationships
  436. WHERE (from_entity = ? AND to_entity = ?)
  437. OR (from_entity = ? AND to_entity = ?)
  438. ORDER BY chapter DESC
  439. """,
  440. (entity1, entity2, entity2, entity1),
  441. )
  442. return [dict(row) for row in cursor.fetchall()]
  443. def get_recent_relationships(self, limit: int = 30) -> List[Dict]:
  444. """获取最近建立的关系"""
  445. with self._get_conn() as conn:
  446. cursor = conn.cursor()
  447. cursor.execute(
  448. """
  449. SELECT * FROM relationships
  450. ORDER BY chapter DESC, id DESC
  451. LIMIT ?
  452. """,
  453. (limit,),
  454. )
  455. return [dict(row) for row in cursor.fetchall()]
  456. # ==================== v5.3 Override Contract 操作 ====================
  457. def update_entity_field(self, entity_id: str, field: str, value: Any) -> bool:
  458. """Compatibility helper to update a single entity field in current_json."""
  459. return self.update_entity_current(entity_id, {field: value})