Wire up query history: localStorage session persistence + sidebar UI

This commit is contained in:
2026-08-14 17:44:36 +07:00
parent 9be5819710
commit 057d4ed9dc
23 changed files with 1231 additions and 30 deletions
+40 -4
View File
@@ -30,7 +30,7 @@ from .routing import QueryRoutingService
# be different prompts (or a different judge) to be independent evidence —
# see this function's own reasoning above.
_QUICK_REPLY_MAX_ITEMS = 18 # one per monograph section (see rag/sections.py SECTION_ORDER)
_QUICK_REPLY_MAX_ITEMS = 19 # one per monograph section (see rag/sections.py SECTION_ORDER)
_QUICK_REPLY_MAX_CHARS = 40
logger = logging.getLogger(__name__)
@@ -368,6 +368,39 @@ def _presentation_for_sources(source_ids: tuple[str, ...]) -> tuple[str, str]:
return "Trả lời", "fact_list"
# Feature-List #14: a condition/symptom -> drug reverse lookup
# (`list_mode=True`, `RagAgent._condition_to_drug`) states which drugs the
# formulary's `chi_dinh` sections name for this indication — a factual
# lookup, never a treatment ranking (see
# `[[feedback_no_recommendation_gate]]`) — but rendered as a bare drug list
# it reads exactly like a recommendation. Fixed, non-generated block, same
# guarantee as `DISCLAIMER` above: the model never sees or writes this text,
# so it cannot reword, shorten, or drop it. Always the first block; `kind`
# matches `_plan_answer`'s forced `needs_warning=True` for `list_mode`, so
# the UI renders it with the same visual separation as any other warning
# block, not a plain bullet.
_LIST_MODE_NOTICE_BLOCK = AnswerBlock(
title="Đọc cho đúng",
kind="warning",
claims=(
AnswerClaim(
text=(
"Đây là kết quả TRA CỨU các thuốc mà Dược thư Quốc gia Việt "
"Nam ghi nhận chỉ định cho triệu chứng/bệnh này, KHÔNG PHẢI "
"khuyến nghị điều trị hay chỉ định lâm sàng cho một người "
"bệnh cụ thể."
),
),
),
)
def _with_list_mode_notice(
blocks: tuple[AnswerBlock, ...], list_mode: bool
) -> tuple[AnswerBlock, ...]:
return (_LIST_MODE_NOTICE_BLOCK, *blocks) if list_mode else blocks
def _build_blocks(
claims: tuple[tuple[str, tuple[int, ...]], ...],
indexed: list[tuple[str, Citation]],
@@ -437,7 +470,10 @@ def _plan_answer(query: str, result: RetrievalResult, list_mode: bool) -> Answer
layout=layout,
reasoning_mode="synthesis" if multi_source else "direct_lookup",
show_heading=multi_source or verbosity == "detailed",
needs_warning=any(section in _WARNING_SECTIONS for section in sections),
# `list_mode` always carries `_LIST_MODE_NOTICE_BLOCK` (see below) —
# this is what gives that block its actual warning presentation in
# the UI, not just a plain untitled bullet.
needs_warning=list_mode or any(section in _WARNING_SECTIONS for section in sections),
)
@@ -677,7 +713,7 @@ class GroundedAnswerService:
(text, (index,))
for index, text in enumerate(evidence_texts, start=1)
)
blocks = _build_blocks(claims, indexed)
blocks = _with_list_mode_notice(_build_blocks(claims, indexed), list_mode)
return GroundedAnswer(
result,
"\n".join(text for text, _ in claims),
@@ -724,7 +760,7 @@ class GroundedAnswerService:
if 1 <= index <= len(indexed)
))
citations = tuple(indexed[index - 1][1] for index in cited_indices)
blocks = _build_blocks(outcome.claims, indexed)
blocks = _with_list_mode_notice(_build_blocks(outcome.claims, indexed), list_mode)
clean_answer = "\n".join(text for text, _ in outcome.claims)
self._metrics.increment(metric_names.GENERATION_SERVED)
return GroundedAnswer(