Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 35 additions & 2 deletions application/engine/services/query_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -566,8 +566,31 @@ def get_workbench_context(self, novel_id: str) -> WorkbenchContextResponse:
logger.debug(f"共享内存中没有小说 {novel_id} 的工作台数据,从数据库加载")
return self._fallback_workbench_from_db(novel_id)

# 旧启动链路可能只预热了章节/故事线,导致作品基础、知识库、剧情弧光等为空。
# 这里对缺失面板做一次按需回填,避免 UI 看起来像“基础没有填”。
if not chronicles or plot_arc is None or knowledge is None or not foreshadows:
try:
from application.engine.services.state_bootstrap import StateBootstrap

bootstrap = StateBootstrap()
if not foreshadows:
foreshadows = bootstrap._load_foreshadows(novel_id)
if plot_arc is None:
plot_arc = bootstrap._load_plot_arc(novel_id)
if knowledge is None:
knowledge = bootstrap._load_knowledge(novel_id)
if not chronicles:
# 编年史依赖 Bible / snapshots / chapters,按需补齐依赖后再聚合。
bootstrap._load_bible(novel_id)
bootstrap._load_snapshots(novel_id)
chapters = self._shared.get_chapters(novel_id) or chapters
chronicles = bootstrap._load_chronicles(novel_id)
except Exception as e:
logger.debug("工作台上下文按需回填失败: novel=%s err=%s", novel_id, e)

# 计算最大章节号
max_ch = max((c.number for c in chapters), default=1)
knowledge_graph = self.get_knowledge_graph(novel_id)

return WorkbenchContextResponse(
novel_id=novel_id,
Expand All @@ -581,7 +604,7 @@ def get_workbench_context(self, novel_id: str) -> WorkbenchContextResponse:
plot_arc=plot_arc,
knowledge=knowledge,
foreshadow_ledger=foreshadows,
knowledge_graph={"total_triples": 0, "by_source": {}}, # 需要单独处理
knowledge_graph=knowledge_graph,
macro={"narrative_event_count": 0}, # 需要单独处理
sandbox={"bible_character_count": 0}, # 需要单独处理
chapters_digest=[c.to_dict() for c in chapters],
Expand All @@ -601,8 +624,18 @@ def _fallback_workbench_from_db(self, novel_id: str) -> WorkbenchContextResponse
chapters_data = bootstrap._load_chapters(novel_id)
foreshadows_data = bootstrap._load_foreshadows(novel_id)
plot_arc_data = bootstrap._load_plot_arc(novel_id)
bootstrap._load_bible(novel_id)
triples_data = bootstrap._load_triples(novel_id)
bootstrap._load_snapshots(novel_id)
knowledge_data = bootstrap._load_knowledge(novel_id)
chronicles_data = bootstrap._load_chronicles(novel_id)
knowledge_graph = {
"total_triples": len(triples_data),
"by_source": {},
}
for t in triples_data:
src = t.get("source_type", "unknown")
knowledge_graph["by_source"][src] = knowledge_graph["by_source"].get(src, 0) + 1

max_ch = max((c.number for c in chapters_data), default=1) if chapters_data else 1

Expand All @@ -618,7 +651,7 @@ def _fallback_workbench_from_db(self, novel_id: str) -> WorkbenchContextResponse
plot_arc=plot_arc_data,
knowledge=knowledge_data,
foreshadow_ledger=foreshadows_data,
knowledge_graph={"total_triples": 0, "by_source": {}},
knowledge_graph=knowledge_graph,
macro={"narrative_event_count": 0},
sandbox={"bible_character_count": 0},
chapters_digest=[c.to_dict() for c in chapters_data],
Expand Down
121 changes: 121 additions & 0 deletions application/engine/services/state_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ def load_all(self) -> Dict[str, Any]:
# 加载 Bible
self._load_bible(novel_id)

# 加载叙事知识(premise lock / 章后摘要 / facts)
self._load_knowledge(novel_id)

# 加载三元组
self._load_triples(novel_id)

Expand Down Expand Up @@ -135,6 +138,21 @@ def load_novel(self, novel_id: str) -> bool:
# 加载剧情弧光
self._load_plot_arc(novel_id)

# 加载 Bible
self._load_bible(novel_id)

# 加载叙事知识
self._load_knowledge(novel_id)

# 加载三元组
self._load_triples(novel_id)

# 加载快照
self._load_snapshots(novel_id)

# 加载编年史
self._load_chronicles(novel_id)

return True

except Exception as e:
Expand Down Expand Up @@ -293,6 +311,31 @@ def _load_foreshadows(self, novel_id: str) -> List[Dict[str, Any]]:
self._shared.set_foreshadows(novel_id, entries)
return entries

rows = db.fetch_all(
"""SELECT id, description, planted_chapter, due_chapter,
resolved_chapter, status, importance, subtext_type
FROM foreshadows
WHERE novel_id = ?
ORDER BY planted_chapter, importance DESC, id""",
(novel_id,),
)
entries = [
{
"id": r["id"],
"description": r["description"],
"planted_chapter": r["planted_chapter"],
"due_chapter": r["due_chapter"],
"resolved_chapter": r["resolved_chapter"],
"status": r["status"],
"importance": r["importance"],
"subtext_type": r["subtext_type"],
}
for r in rows
]
if entries:
self._shared.set_foreshadows(novel_id, entries)
return entries

return []

except Exception as e:
Expand Down Expand Up @@ -425,6 +468,77 @@ def _load_bible(self, novel_id: str) -> Optional[Dict[str, Any]]:
logger.debug(f"加载 Bible 失败(可能不存在): {novel_id}, {e}")
return None

def _load_knowledge(self, novel_id: str) -> Optional[Dict[str, Any]]:
"""加载叙事知识到共享内存。

workbench-context 的 DB fallback 已经依赖该方法;缺失时会导致
“作品基础/知识库/章后摘要”在共享状态或降级读取中全部为空。
"""
try:
from domain.novel.value_objects.novel_id import NovelId
from infrastructure.persistence.database.connection import get_database
from infrastructure.persistence.database.sqlite_knowledge_repository import (
SqliteKnowledgeRepository,
)

repo = SqliteKnowledgeRepository(get_database())
knowledge = repo.get_by_novel_id(NovelId(novel_id))

if not knowledge:
return None

chapters = [
{
"chapter_id": ch.chapter_id,
"summary": ch.summary,
"key_events": ch.key_events,
"open_threads": ch.open_threads,
"consistency_note": ch.consistency_note,
"beat_sections": list(ch.beat_sections or []),
"micro_beats": list(ch.micro_beats or []),
"sync_status": ch.sync_status,
}
for ch in (knowledge.chapters or [])
]

facts = [
{
"id": fact.id,
"subject": fact.subject,
"predicate": fact.predicate,
"object": fact.object,
"chapter_id": fact.chapter_id,
"note": fact.note,
"entity_type": fact.entity_type,
"importance": fact.importance,
"location_type": fact.location_type,
"description": fact.description,
"first_appearance": fact.first_appearance,
"related_chapters": list(fact.related_chapters or []),
"tags": list(fact.tags or []),
"attributes": dict(fact.attributes or {}),
"confidence": fact.confidence,
"source_type": fact.source_type,
"subject_entity_id": fact.subject_entity_id,
"object_entity_id": fact.object_entity_id,
"provenance": list(getattr(fact, "provenance", []) or []),
}
for fact in (knowledge.facts or [])
]

knowledge_dict = {
"version": knowledge.version,
"premise_lock": knowledge.premise_lock,
"chapters": chapters,
"facts": facts,
}
self._shared.set_knowledge(novel_id, knowledge_dict)
return knowledge_dict

except Exception as e:
logger.debug(f"加载叙事知识失败(可能不存在): {novel_id}, {e}")
return None

def _load_triples(self, novel_id: str) -> List[Dict[str, Any]]:
"""加载三元组到共享内存"""
try:
Expand Down Expand Up @@ -567,6 +681,13 @@ def refresh_narrative_contract_in_shared_state(novel_id: str) -> None:
try:
bootstrap = StateBootstrap()
bootstrap._load_bible(novel_id)
bootstrap._load_chapters(novel_id)
bootstrap._load_foreshadows(novel_id)
bootstrap._load_storylines(novel_id)
bootstrap._load_plot_arc(novel_id)
bootstrap._load_knowledge(novel_id)
bootstrap._load_triples(novel_id)
bootstrap._load_snapshots(novel_id)
bootstrap._load_chronicles(novel_id)
except Exception as e:
logger.debug("叙事契约共享状态刷新失败 novel=%s err=%s", novel_id, e)