mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-18 21:57:27 +08:00
fix: render readable wiki artifact links (#17011)
This commit is contained in:
@@ -38,6 +38,7 @@ import json
|
||||
import logging
|
||||
import re
|
||||
from typing import Callable, Optional
|
||||
from urllib.parse import urlsplit
|
||||
from common.misc_utils import thread_pool_exec
|
||||
from common.token_utils import num_tokens_from_string
|
||||
from rag.prompts.generator import gen_json, message_fit_in
|
||||
@@ -2841,19 +2842,20 @@ async def _wiki_build_source_context(
|
||||
|
||||
_WIKILINK_PIPE_RE = re.compile(r"\[\[([^\[\]\|]+?)\|([^\[\]]+?)\]\]")
|
||||
_WIKILINK_SIMPLE_RE = re.compile(r"\[\[([^\[\]\|]+?)\]\]")
|
||||
_ARTIFACT_MARKDOWN_LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
|
||||
|
||||
|
||||
def _wiki_transform_links(content_md: str, kb_id: str) -> tuple[str, list[str]]:
|
||||
"""Rewrite ``[[slug]]`` / ``[[slug|display]]`` wikilinks to standard
|
||||
markdown links whose href encodes ``(kb_id, slug)`` so a renderer can
|
||||
fetch the target page from ES.
|
||||
def _wiki_transform_links(content_md: str, kb_id: str, page_titles: dict[str, str] | None = None) -> tuple[str, list[str]]:
|
||||
"""Normalize wiki links and return ``(rendered_md, unique_outlinks)``.
|
||||
|
||||
Returns ``(rewritten_md, unique_outlinks)`` — outlinks are slug strings
|
||||
in first-seen order. The href format is ``artifact/{kb_id}/{slug}`` which is
|
||||
relative; clients are expected to map this to whatever route serves the
|
||||
page (e.g. ``/api/v1/artifact/{kb_id}/{slug}``).
|
||||
Both the canonical ``[[slug]]`` form and Markdown links emitted by an LLM
|
||||
are accepted. Artifact links are rewritten to the relative
|
||||
``artifact/{kb_id}/{slug}`` form. When an artifact link uses its slug as
|
||||
the label, prefer the planned page title; otherwise derive readable text
|
||||
from the slug.
|
||||
"""
|
||||
kb_id_str = str(kb_id)
|
||||
page_titles = page_titles or {}
|
||||
seen: set[str] = set()
|
||||
outlinks: list[str] = []
|
||||
|
||||
@@ -2863,6 +2865,38 @@ def _wiki_transform_links(content_md: str, kb_id: str) -> tuple[str, list[str]]:
|
||||
seen.add(s)
|
||||
outlinks.append(s)
|
||||
|
||||
def _display_text(label: str, slug: str) -> str:
|
||||
label = label.strip()
|
||||
if label not in {slug, slug.rsplit("/", 1)[-1]}:
|
||||
return label
|
||||
planned_title = page_titles.get(slug)
|
||||
if planned_title:
|
||||
return planned_title
|
||||
readable = slug.rsplit("/", 1)[-1].replace("-", " ").replace("_", " ").strip()
|
||||
return readable.title() or label
|
||||
|
||||
def _artifact_slug(href: str) -> str | None:
|
||||
parsed = urlsplit(href)
|
||||
if parsed.scheme or parsed.netloc:
|
||||
if parsed.netloc != "artifact":
|
||||
return None
|
||||
path = parsed.path
|
||||
else:
|
||||
path = parsed.path
|
||||
parts = path.strip("/").split("/")
|
||||
if parts and parts[0] == "artifact":
|
||||
parts = parts[1:]
|
||||
if len(parts) < 2 or parts[0] != kb_id_str:
|
||||
return None
|
||||
return "/".join(parts[1:])
|
||||
|
||||
def _markdown_artifact(m: re.Match) -> str:
|
||||
slug = _artifact_slug(m.group(2))
|
||||
if not slug:
|
||||
return m.group(0)
|
||||
_track(slug)
|
||||
return f"[{_display_text(m.group(1), slug)}](artifact/{kb_id_str}/{slug})"
|
||||
|
||||
def _piped(m: re.Match) -> str:
|
||||
slug = m.group(1).strip()
|
||||
text = m.group(2).strip()
|
||||
@@ -2872,9 +2906,10 @@ def _wiki_transform_links(content_md: str, kb_id: str) -> tuple[str, list[str]]:
|
||||
def _simple(m: re.Match) -> str:
|
||||
slug = m.group(1).strip()
|
||||
_track(slug)
|
||||
return f"[{slug}](artifact/{kb_id_str}/{slug})"
|
||||
return f"[{_display_text(slug, slug)}](artifact/{kb_id_str}/{slug})"
|
||||
|
||||
rewritten = _WIKILINK_PIPE_RE.sub(_piped, content_md or "")
|
||||
rewritten = _ARTIFACT_MARKDOWN_LINK_RE.sub(_markdown_artifact, content_md or "")
|
||||
rewritten = _WIKILINK_PIPE_RE.sub(_piped, rewritten)
|
||||
rewritten = _WIKILINK_SIMPLE_RE.sub(_simple, rewritten)
|
||||
return rewritten, outlinks
|
||||
|
||||
@@ -3362,6 +3397,11 @@ async def wiki_refine_from_plan(
|
||||
all_claims = plan.get("_claims") or []
|
||||
# ``all_plan_slugs`` is implicitly deduped now (pages_spec is unique).
|
||||
all_plan_slugs = [p["slug"] for p in pages_spec]
|
||||
page_titles = {
|
||||
str(p["slug"]): str(p.get("title") or "").strip()
|
||||
for p in pages_spec
|
||||
if p.get("slug") and str(p.get("title") or "").strip()
|
||||
}
|
||||
|
||||
# Build canonical entity/concept lookups for evidence fallback. When MAP
|
||||
# produced no claims (a real failure mode we've seen on Chinese / dense
|
||||
@@ -3483,7 +3523,7 @@ async def wiki_refine_from_plan(
|
||||
)
|
||||
|
||||
# Render artifactlinks once, here, after all LLM transforms.
|
||||
content_md_rendered, outlinks = _wiki_transform_links(content_md_raw, kb_id)
|
||||
content_md_rendered, outlinks = _wiki_transform_links(content_md_raw, kb_id, page_titles=page_titles)
|
||||
source_doc_ids = await _wiki_collect_doc_ids(source_chunk_ids, tenant_id, kb_id)
|
||||
summary = _wiki_extract_summary(content_md_rendered) or title
|
||||
|
||||
|
||||
Reference in New Issue
Block a user